版权声明:本文为博主原创文章,未经博主允许不得转载。 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 "选课成功";
}
}