1. 程式人生 > >mybatis 使用for update,對資料進行行級鎖定

mybatis 使用for update,對資料進行行級鎖定

與Spring Date JPA中使用行級鎖一樣,都需要加上事務,並在查詢的時候加上for update。直接上程式碼:

資料庫表:

create table t_pub_student(
id int PRIMARY key auto_increment,
code VARCHAR(50) COMMENT '學生CODE',
name VARCHAR(50) COMMENT '學生名字'
)

create table t_course_detail(
id int PRIMARY key auto_increment,
name VARCHAR(50) COMMENT '課程名稱',
teacher_name VARCHAR(50) COMMENT '教師名字',
elective_total int COMMENT '可選總數',
elective_num int COMMENT '已選數量'
)

create table t_course_detail(
id int PRIMARY key auto_increment,
student_code varchar(50) COMMENT '學生code',
course_id int COMMENT '課程ID'
)

程式碼目錄結構:

定義實體:

package course.entity;

import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;

import javax.persistence.*;

/**
 * Created by Xichuan on 2018-10-31.
 */
@TableName("t_pub_student")
public class Student {

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;

    @Column(name = "code")
    private String code;

    @Column(name = "name")
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package course.entity;

import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;

import javax.persistence.*;

/**
 * Created by Xichuan on 2018-10-31.
 */
@TableName("t_pub_course")
public class Course {

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;

    @Column(name = "name")
    private String name;

    @Column(name = "teacher_name")
    private String teacherName;

    @Column(name = "elective_total")
    private Integer electiveTotal;

    @Column(name = "elective_num")
    private Integer electiveNum;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getTeacherName() {
        return teacherName;
    }

    public void setTeacherName(String teacherName) {
        this.teacherName = teacherName;
    }

    public Integer getElectiveTotal() {
        return electiveTotal;
    }

    public void setElectiveTotal(Integer electiveTotal) {
        this.electiveTotal = electiveTotal;
    }

    public Integer getElectiveNum() {
        return electiveNum;
    }

    public void setElectiveNum(Integer electiveNum) {
        this.electiveNum = electiveNum;
    }
}
package course.entity;

import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;

import javax.persistence.*;

/**
 * Created by Xichuan on 2018-10-31.
 */
@TableName("t_course_detail")
public class CourseDetail {

    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;

    @Column(name = "course_id")
    private Integer courseId;

    @Column(name = "student_code")
    private String studentCode;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getCourseId() {
        return courseId;
    }

    public void setCourseId(Integer courseId) {
        this.courseId = courseId;
    }

    public String getStudentCode() {
        return studentCode;
    }

    public void setStudentCode(String studentCode) {
        this.studentCode = studentCode;
    }
}

DAO層程式碼:

package course.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import course.entity.Student;
import org.apache.ibatis.annotations.Mapper;

/**
 * Created by XiChuan on 2018-10-31.
 */
@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}
package course.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import course.entity.CourseDetail;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface CourseDetailMapper extends BaseMapper<CourseDetail> {
}
package course.mapper;

import com.baomidou.mybatisplus.mapper.BaseMapper;
import course.entity.Course;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

/**
 * Created by Xichuan on 2018-10-31.
 */
public interface CourseMapper extends BaseMapper<Course> {

    /**將此行資料進行加鎖,當整個方法將事務提交後,才會解鎖*/
    @Select(value = "select t from t_pub_course t where t.id = #{courseId} ")
    Course queryAllById(@Param("courseId") Integer courseId);

    /**將course表中的electiveNum進行加1操作*/
    @Update("update t_pub_course t set t.elective_num = t.elective_num + 1 where t.id = #{courseId}")
    void addElectiveNumByCourseId(@Param("courseId") Integer courseId);
}

定義介面:

package course.controller;

import course.service.CourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by XiChuan on 2018-10-31.
 */
@RestController
public class CourseController {

    @Autowired
    CourseService courseService;

    @PostMapping("/course/choose")
    public Object chooseCourse(@RequestParam("student_code")String studentCode,
                               @RequestParam("course_id")Integer courseId){
        return  courseService.chooseCourse(studentCode,courseId);
    }
}

service層程式碼:

public interface CourseService {
    Object chooseCourse(String studentCode,Integer courseId);
}
package course.service.impl;

import course.entity.Course;
import course.entity.CourseDetail;
import course.mapper.CourseDetailMapper;
import course.mapper.CourseMapper;
import course.mapper.StudentMapper;
import course.service.CourseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;

/**
 * Created by XiChuan on 2018-10-31.
 */
@Service
public class CourseServiceImpl implements CourseService {
    private Logger logger = LoggerFactory.getLogger(CourseServiceImpl.class);

    @Autowired
    StudentMapper studentMapper;

    @Autowired
    CourseMapper courseMapper;

    @Autowired
    CourseDetailMapper courseDetailMapper;


    /**使用for update一定要加上這個事務
     * 當事務處理完後,for update才會將行級鎖解除*/
    @Transactional(isolation = Isolation.READ_COMMITTED)
    // @Transactional(value = "testTransactionManager") //如果是多資料來源,需要制定資料來源
    @Override
    public Object chooseCourse(String studentCode, Integer courseId) {

        /** courseRepository.queryAllById(courseId)會對所選中的那條記錄加行級鎖,其他執行緒會在此排隊,當事務提交後,才會進行解鎖*/
        Course course = courseMapper.queryAllById(courseId);

        int electiveNum = course.getElectiveNum();
        int totalNum = course.getElectiveTotal();
        logger.info("After Lock Step 1, Thread: {},courseId{}, studentId: {}, electiveNum: {}, total: {}", Thread.currentThread(),courseId,studentCode, electiveNum, totalNum);

        if (Objects.isNull(course)){
            return "課程不存在";
        }
        if (electiveNum >= totalNum) {
            return "此課程已被選完";
        }

        /**將此此學生的選課資訊儲存到選課詳情裡面*/
        CourseDetail courseDetail = new CourseDetail();
        courseDetail.setCourseId(courseId);
        courseDetail.setStudentCode(studentCode);
        courseDetailMapper.insert(courseDetail);

        /**將course表中的electiveNum進行加1操作
         * 使用sql進行累加更加安全,因為使用方法開始查詢的course中的electiveNum,並不一定是資料庫儲存的值*/
        courseMapper.addElectiveNumByCourseId(courseId);
        return "選課成功";
    }
}