1. 程式人生 > >專案實際開發中遇到的事務問題

專案實際開發中遇到的事務問題

廢話不多說 看功能:
最近做了一個app的記步功能,要求是app一開啟就開始進行步數的計算,然後一分鐘向後臺傳送一次資料儲存到資料庫中,此時儲存的是使用者的走的步數和有效步數(有效步數是在一分鐘內步頻大於90的算是真正的走路)和有效時間,然後當你開啟記步頁面的時候,又一個請求一分鐘一次的向後天傳資料,此時儲存的資料是使用者走的實際的里程和使用者從開啟手機到現在累計消耗的能量,先看實體類的設計

package com.kd.food.model;

import com.kd.framework.data.orm.BaseModel;

/**
 * @author 使用者記步實體類
 *
 */
public class RecordStep extends BaseModel { private static final long serialVersionUID = -5858855168283954031L; /** * 使用者ID */ private String userId; private int count; /** * 今日有效步數 */ private int effectiveSteps; /** * 今日實際步數 */ private Integer actualStep; /** * 今日目標步數 */
private Integer targetStep; /** * 今日實際運動時間 */ private Integer actualTime; /** * 今日目標運動時間 */ private Integer targetTime; /** * 今日實際運動里程 */ private String actualMileage; /** * 今日目標運動里程 */ private String targetMileage; /** * 今日消實際耗能量 */
private Integer actualEnergy; /** * 今日目標能量 */ private Integer targetEnergy; /** * 刪除 */ private String deleteFlag; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Integer getActualStep() { return actualStep; } public void setActualStep(Integer actualStep) { this.actualStep = actualStep; } public Integer getTargetStep() { return targetStep; } public void setTargetStep(Integer targetStep) { this.targetStep = targetStep; } public Integer getActualTime() { return actualTime; } public void setActualTime(Integer actualTime) { this.actualTime = actualTime; } public Integer getTargetTime() { return targetTime; } public void setTargetTime(Integer targetTime) { this.targetTime = targetTime; } public String getActualMileage() { return actualMileage; } public void setActualMileage(String actualMileage) { this.actualMileage = actualMileage; } public String getTargetMileage() { return targetMileage; } public void setTargetMileage(String targetMileage) { this.targetMileage = targetMileage; } public Integer getActualEnergy() { return actualEnergy; } public void setActualEnergy(Integer actualEnergy) { this.actualEnergy = actualEnergy; } public Integer getTargetEnergy() { return targetEnergy; } public void setTargetEnergy(Integer targetEnergy) { this.targetEnergy = targetEnergy; } public static long getSerialversionuid() { return serialVersionUID; } public String getDeleteFlag() { return deleteFlag; } public void setDeleteFlag(String deleteFlag) { this.deleteFlag = deleteFlag; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public int getEffectiveSteps() { return effectiveSteps; } public void setEffectiveSteps(int effectiveSteps) { this.effectiveSteps = effectiveSteps; } }

controller層 這是app一開啟就開始的請求,現在需要儲存的資料是實際的步數,有效時間,有效步數:


    /**
     * 記步請求 一分鐘請求一次 
     * @return
     */
    @RequestMapping("/saveStep")
    @ResponseBody
    public String saveStep(HttpServletRequest request,HttpServletResponse response){
        try {
            String userId = request.getParameter("id");//使用者ID
            String step = request.getParameter("step");//實際的步數
            String time = request.getParameter("effectiveTime");//有效運動時間
            String effectiveSteps = request.getParameter("effectiveSteps");//有效步數

            RecordStep recordStep=new RecordStep();
            recordStep.setUserId(userId);
            if (StringUtil.isEmpty(step)) {
                step="0";
            }
            recordStep.setActualStep(DataConvert.getInt(step));
            if(StringUtil.isEmpty(effectiveSteps)){
                effectiveSteps="0";
            }
            recordStep.setEffectiveSteps(DataConvert.getInt(effectiveSteps));
            if (StringUtil.isEmpty(time)) {
                time="0";
            }
            recordStep.setActualTime(DataConvert.getInt(time));
            recordStep.setCreateBy(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));

            recordStepService.saverecordStep(recordStep);
            JSONObject json=new JSONObject();
            json.put("success", true);
            return json.toJSONString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "ok";
    }

這是記步頁面開啟的時候開始的請求 ,此時需要儲存的是使用者走的里程和實際消耗的能量

/**
     * 開啟記步頁面 儲存里程和卡路里 (一分鐘儲存一次實際里程和消耗能量)
     * (上傳實時運動資料 里程和能量的消耗)
     * @return
     */
    @RequestMapping("/updateStep")
    @ResponseBody
    public String querySteps(HttpServletRequest request,HttpServletResponse response){
        JSONObject json=new JSONObject();
        try {
            String userId = request.getParameter("id");//使用者ID
            String licheng = request.getParameter("sumJourney");//實際里程
            String enenrgy = request.getParameter("sumKcal");//消耗能量
            RecordStep recordStep=new RecordStep();
            recordStep.setUserId(userId);
            //如果傳來NaN
            if("NaN".equals(licheng)){
                return json.toJSONString();
            }
            //實際里程為0 則沒走 也沒消耗能量 直接返回唄
            if (StringUtil.isEmpty(licheng)||"0".equals(licheng)) {
                return json.toJSONString();
            }
            recordStep.setActualMileage(licheng);
            if (StringUtil.isEmpty(enenrgy)) {
                enenrgy="0";
            }
            recordStep.setActualEnergy(DataConvert.getInt(enenrgy));
            recordStep.setCreateBy(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
            boolean flag = recordStepService.saverecordStep(recordStep);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return json.toJSONString();
    }

這是你會看到 ,兩個請求訪問的是同一個service,因為表的設計是每天儲存使用者一一條資料,這一條資料就是他這一天的步數里程等等,所以當你app開啟的時候判斷當前使用者今天有沒有走路,有了就插入,沒了就更新資料,實時儲存嘛
service實現層:

    /**
     * 儲存步數(更新and插入操作)
     */
    public boolean saverecordStep(RecordStep recordStep) {

        try {
            baseDao.update("RecordStep.insertStep", recordStep);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

這是mapper檔案:有則更新,沒有則插入

<!--  如果存在就更新 不存在則插入-->
    <insert id="insertStep" useGeneratedKeys="true" keyProperty="id" parameterType="RecordStep">
        <selectKey keyProperty="count" resultType="int" order="BEFORE">
            select count(*)  as count from tj_record_step where  CREATEBY=#{createBy} and USER_ID=#{userId}
      </selectKey>
      <!--如果不存在建立  -->
          <if test="count==0">
            INSERT INTO 
            tj_record_step
        (
            <include refid="SRecordColumns" />
        )VALUES(
            #{userId},
            #{actualTime},
            #{actualEnergy},
            #{actualMileage},
            #{actualStep},
            #{effectiveSteps},
            NOW(),
            NOW()
        )
        </if>
        <!--如果存在,更新時間  -->
        <if test="count > 0">
            UPDATE tj_record_step SET UPDATEBY=NOW() 
            <if test="actualTime !=null and actualTime !=''">
                ,ACTUAL_TIME=#{actualTime}
            </if>
            <if test="actualEnergy !=null and actualEnergy !=''">
                ,ACTUAL_ENERGY=#{actualEnergy}
            </if>
            <if test="actualMileage !=null and actualMileage !=''">
                ,ACTUAL_MILEAGE=#{actualMileage}
            </if>
            <if test="actualStep !=null and actualStep !=''">
                ,ACTUAL_STEP=#{actualStep}
            </if>
            <if test="effectiveSteps !=null and effectiveSteps !=''">
                ,EFFECTIVE_STEPS=#{effectiveSteps}
            </if>
            WHERE  USER_ID=#{userId} and CREATEBY=#{createBy}
        </if>
    </insert>

當時測試的時候是在瀏覽器上,沒有考慮到併發情況,測試的時候都是單執行緒情況,沒一點問題,當撞到手機上之後,我曹,簡直驚呆了。,因為要把使用者的每天走路的步數用圖表展示起來,然後使用者昨天的資料以及前天的資料全部被更新了,然後我趕緊檢視資料庫,我曹,竟然全部被更新了,而且,同時插入了兩條,一臉矇蔽啊,百思不得其解,,,,然後呢,才想到,他媽併發的情況所以呢想到了用事務,

/**
     * 儲存步數(更新and插入操作)
     * Isolation.READ_COMMITTED 隔離級別 防止髒讀
     */
    @Transactional(isolation=Isolation.READ_COMMITTED)
    public boolean saverecordStep(RecordStep recordStep) {

        try {
            baseDao.update("RecordStep.insertStep", recordStep);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

ok 解決了