MySQL乐观锁并发控制实现案例

文章展示了如何在MySQL中使用乐观锁进行并发控制,通过在`t_point`表中添加`version`字段,配合MyBatis的Mapper接口实现更新操作。在更新时,首先获取记录的当前版本号,然后尝试更新,如果版本号不匹配(即被其他事务修改),更新将失败。示例提供了带有延迟的并发测试用例,用于演示乐观锁防止数据冲突的机制。
摘要由CSDN通过智能技术生成

MySQL乐观锁并发控制实现案例

表设计

CREATE TABLE `t_point` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `point_name` varchar(255) NOT NULL,
  `point_type` varchar(255) NOT NULL,
  `version` int(11) NOT NULL DEFAULT '1',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4

mybatis各层设计

实体层

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Point implements Serializable {
    private static final long serialVersionUID = -6823782099362425089L;

    private Integer id;

    private String pointName;

    private String pointType;

    private Integer version;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

Mapper层

PointMapper.java代码

@Mapper
public interface PointMapper {


    @Select(value = "select * from t_point where id =#{id}")
    @Results(value = {
            @Result(column = "create_time",property = "createTime",javaType = LocalDateTime.class,jdbcType = JdbcType.TIMESTAMP),
            @Result(column = "update_time",property = "updateTime",javaType = LocalDateTime.class,jdbcType = JdbcType.TIMESTAMP),

    })
    Point findById(@Param("id") Integer id);






    @Update(value = "update t_point set point_name=#{pointName},point_type =#{pointType},version=version+1 where id =#{id}")
    int update(Point point);


    /**
     * 乐观锁
     * 1.每次更新 version=version+1
     * 2.需要携带version字段进行更新 ,若version 不一致,则不会更新成功
     * 3.使用乐观锁更新前的select查询是不能带锁的
     *
     */
    @Update(value = "update t_point set point_name=#{pointName},point_type =#{pointType},version=version+1 where id =#{id} and version =#{version}")
    int updateWithVersion(Point point);


    @Select(value = "select * from t_point where point_name =#{name}")
    Point findByPointName(@Param("name") String name);


    @Insert(value = "insert into t_point (point_name ,point_type) values (#{pointName},#{pointType})")
    int save(Point point);



    int updateBatch(List<Point> points);




}

PointMapper.xml 代码

<?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.example.demo.orm.dao.PointMapper">


<!--
foreach组装多条update语句,来形成分号;间隔的一条语句;
执行时需要jdbcUrl加上&allowMultiQueries=true
-->
    <update id="updateBatch" parameterType="java.util.List">
        <foreach collection="list" item="item" index="index" open="" close="" separator=";">
            update t_point
            <set>
                point_name = #{item.pointName},
                point_type = #{item.pointType},
                version=version+1
            </set>
            where id = #{item.id}
        </foreach>
    </update>
    
</mapper>

web接口及业务层

业务层

@Slf4j
@Service
public class PointService {
    @Resource
    private PointMapper pointMapper;
    
    @Transactional
    public void updateWithTimeOptimistic(Point point,int time) throws Exception {
        Point byPointName = pointMapper.findByPointName(point.getPointName());
        if (byPointName==null){
            return;
        }
        if (StrUtil.isNotBlank(point.getPointType())){
            byPointName.setPointType(point.getPointType());
        }
        if(StrUtil.isNotBlank(point.getPointName())){
            byPointName.setPointName(point.getPointName());
        }
        Thread.sleep(time*1000L);
        int ret = pointMapper.updateWithVersion(byPointName);
        if (ret<1){
            log.info("乐观更新-版本不一致-导致更新失败");
            throw new Exception("乐观更新-版本不一致-导致更新失败!!!");
        }

    }

    @Transactional
    public void updateOptimistic(Point point) throws Exception {
        Point byPointName = pointMapper.findByPointName(point.getPointName());
        if (byPointName==null){
            return;
        }
        if (StrUtil.isNotBlank(point.getPointType())){
            byPointName.setPointType(point.getPointType());
        }
        if(StrUtil.isNotBlank(point.getPointName())){
            byPointName.setPointName(point.getPointName());
        }
        int ret = pointMapper.updateWithVersion(byPointName);
        if (ret<1){
            log.info("乐观锁导致更新失败");
            throw new Exception("乐观锁导致更新失败!!!");
        }

    }
    
}

web接口层

@RestController
public class OptimisticTestController {

    @Autowired
    private PointService pointService;
    
    
     //乐观锁 在数据库的访问中使用,表现为:每次更新都会先查询version,
    // 执行update时会判定version是否相等,只有version相等才会更新成功;
    //
    //
    //
    //先访问
    //http://localhost:8615/opt/update/20
    //接着立马访问
    //http://localhost:8615/opt/update
    //
    //会发现,第二个请求会立刻完成。
    // 而第一个请求因为拿到的version已经是老的值所以最后更新失败,
    // (这里更新时返回值是0,即更新不成功,需要自己写抛出异常,
    // mybatis需要程序员自己判断并抛出异常,而JPA不需要,jpa的乐观锁可以抛异常)
    @RequestMapping("/opt/update/{time}")
    public String update1(@RequestBody Point point, @PathVariable("time") int time) throws Exception {
        pointService.updateWithTimeOptimistic(point, time);
        return "0000";
    }

    @RequestMapping("/opt/update")
    public String update2(@RequestBody Point point) throws Exception{
        pointService.updateOptimistic(point);
        return "0000";
    }
    

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThinkPet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值