在项目的修改操作少的情况下使用乐观锁
演示如何通过数据版本记录机制实现乐观锁
数据库表设计
增加字段version用于记录数据的版本,在执行修改操作时必须要提供version,当提供的version与当前数据记录的version一致时才能修改成功。当修改失败时可以让version自增,重复修改操作直到修改成功,或者直接给前端返回修改失败的结果。
这是一张学生表,记录学生的信息。
create table if not exists tb_student
(
id bigint auto_increment comment '主键id'
primary key,
name varchar(8) not null comment '姓名',
gender int not null comment '性别;1代表男,2代表女',
age int not null comment '年龄',
version int not null comment '版本号',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '更新时间',
constraint id
unique (id)
)
comment '学生表';
insert into tb_student (id, name, gender, age, version, create_time, update_time)
values (null, '艾伦', 1, 15, 0, now(), now());
entity
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentEntity {
private Long id;
private String name;
private Integer gender;
private Integer age;
private Integer version; //版本号,用于乐观锁
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
dto
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentDTO {
private Long id;
private String name;
private Integer gender;
private Integer age;
private Integer version; //版本号,修改操作必须带上
}
controller
import com.demo.pojo.StudentDTO;
import com.demo.service.StudentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("/students")
public class StudentController {
private final StudentService studentService;
@PutMapping("/{id}")
public String updateStudentInfo(@PathVariable Long id, @RequestBody StudentDTO studentDTO) {
try {
studentDTO.setId(id);
studentService.updateStudentInfo(studentDTO);
return "修改成功!";
} catch (Exception ex) {
return "修改失败!";
}
}
}
service
import com.demo.pojo.StudentDTO;
import org.springframework.stereotype.Service;
@Service
public interface StudentService {
void updateStudentInfo(StudentDTO studentDTO);
}
import com.demo.mapper.StudentMapper;
import com.demo.pojo.StudentDTO;
import com.demo.pojo.StudentEntity;
import com.demo.service.StudentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class StudentServiceImpl implements StudentService {
private final StudentMapper studentMapper;
@Transactional
@Override
public void updateStudentInfo(StudentDTO studentDTO) {
StudentEntity studentEntity = new StudentEntity();
BeanUtils.copyProperties(studentDTO, studentEntity);
LocalDateTime now = LocalDateTime.now();
studentEntity.setUpdateTime(now);
Integer result = studentMapper.updateStudentInfo(studentEntity);
//处理一:尝试重新进行修改操作
if (result.equals(0)) {
//修改失败
do {
Integer version = studentEntity.getVersion();
version++; //版本号自增
studentEntity.setVersion(version);
result = studentMapper.updateStudentInfo(studentEntity);
} while (result.equals(0));
}
//处理二:抛出异常,返回修改失败的异常信息
if (result.equals(0)) {
//修改失败
throw new RuntimeException();
}
}
}
mapper
import com.demo.pojo.StudentEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StudentMapper {
Integer updateStudentInfo(StudentEntity studentEntity);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.demo.mapper.StudentMapper">
<!--根据id修改学生信息-->
<update id="updateStudentInfo">
update tb_student
<set>
<if test="name != null and name != ''">name = #{name},</if>
<if test="gender != null">gender = #{gender},</if>
<if test="age != null">age = #{age},</if>
<if test="version != null">version = version + 1,</if>
<if test="updateTime != null">update_time = #{updateTime}</if>
</set>
where id = #{id} and version = #{version};
</update>
</mapper>
yml配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/demo?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&useAffectedRows=true
username: root
password: 123456
mybatis:
mapper-locations: classpath:mapper/*.xml #扫描xml映射文件
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #输出日志
map-underscore-to-camel-case: true #驼峰命名自动映射
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug #事务管理日志
接下来就可以开始测试了,第一种处理方式用循环直到修改成功,第二种是抛出异常,被捕获后返回修改失败的异常信息。两种方式也可以结合起来,比如尝试重新修改一定次数后就放弃修改,返回异常信息。