Spring Data JPA 之 for update

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zc_ad/article/details/83578487

for update问题的由来是由于高并发,且使用负载均衡时使用的。在公司有一个项目的场景,场景并不复杂:学生选课。现在有三张表,1.t_pub_student(学生信息表),2.t_pub_course(课程信息表),3.t_pub_course_detail(学生选课详情)。这三张表的定义分别是:

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

create table t_pub_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'
)

当学生选一门课时,会在t_course_detail插一条数据,在t_course_detail的elective_num 字段+1。

此时就会出现问题,当Thread-1进行选课,t_course_detail插入一条数据后,查新elective_num=10,但出现处理问题,线程暂停。Thread-2此时也进入,进行选课,在 t_course_detail插入数据后,查询elective_num=10(因为Thead-1还没来得及更新t_course,elective_num仍为10,这个就是问题的原因),它继续执行,将elective_num+1操作,elective_num=11退出线程。又过了几秒,Thread-1复活,进行elective_num(值为10,复活前查询的值)+1操作,此时elective_num=11。就会出现t_course_detail有12条记录,t_course中的elective_num值却为11。

 

上面问题可以通过java的锁机制处理,但进行负载均衡的话,锁机制就无法控制了,就需要进行数据库的行级锁处理,即for update。下面我会将处理的逻辑代码分享出来,并添加上详细注释。

代码的目录结构:

定义实体类:

/**学生*/
@Entity
@Table(name = "t_pub_student")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

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

    @Column(name = "name")
    private String name;
    
    Getter...
    Setter...
}
/**课程*/
@Entity
@Table(name = "t_pub_course")
public class Course {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    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;
    
    Getter...
    Setter...
}
/**选课详情*/
@Entity
@Table(name = "t_course_detail")
public class CourseDetail {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

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

    @Column(name = "student_code")
    private String studentCode;
    
    Getter...
    Setter...
}

定义DAO:

public interface StudentRepository extends JpaRepository<Student,Integer>,JpaSpecificationExecutor<Student> {
}
public interface CourseDetailRepository extends JpaRepository<CourseDetail,Integer>,JpaSpecificationExecutor<CourseDetail> {
}
package course.repository;

import course.entity.Course;
import org.springframework.data.jpa.repository.*;
import javax.persistence.LockModeType;

/**
 * Created by Xichuan on 2018-10-31.
 */
public interface CourseRepository extends JpaRepository<Course,Integer>,JpaSpecificationExecutor<Course> {

    /**@Lock 作用的for update作用一样,将此行数据进行加锁,当整个方法将事务提交后,才会解锁*/
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query(value = "select t from Course t where t.id =?1 ")
    Course queryAllById( Integer courseId);

    /**将course表中的electiveNum进行加1操作*/
    @Modifying
    @Query("update Course t set t.electiveNum = t.electiveNum + 1 where t.id =?1")
    void addElectiveNumByCourseId(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 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.repository.CourseDetailRepository;
import course.repository.CourseRepository;
import course.repository.StudentRepository;
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 2018-10-31.
 */
@Service
public class CourseServiceImpl implements CourseService {
    private Logger logger = LoggerFactory.getLogger(CourseServiceImpl.class);

    @Autowired
    StudentRepository studentRepository;

    @Autowired
    CourseRepository courseRepository;

    @Autowired
    CourseDetailRepository courseDetailRepository;


    /**使用for update一定要加上这个事务
     * 当事务处理完后,for update才会将行级锁解除*/
    @Transactional(isolation = Isolation.READ_COMMITTED)
    @Override
    public Object chooseCourse(String studentCode, Integer courseId) {

        /** courseRepository.queryAllById(courseId)会对所选中的那条记录加行级锁,其他线程会在此排队,当事务提交后,才会进行解锁*/
        Course course = courseRepository.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);
        courseDetailRepository.save(courseDetail);

        /**将course表中的electiveNum进行加1操作
         * 使用sql进行累加更加安全,因为使用方法开始查询的course中的electiveNum,并不一定是数据库存储的值*/
        courseRepository.addElectiveNumByCourseId(courseId);
        return "选课成功";
    }
}

猜你喜欢

转载自blog.csdn.net/zc_ad/article/details/83578487