教你使用 JPA 实现乐观锁

80 篇文章 0 订阅
  • 示例
  • 总结

乐观锁的概念就不再赘述了,不了解的朋友请自行百度谷歌之,今天主要说的是在项目中如何使用乐观锁,做成一个小demo。

持久层使用jpa时,默认提供了一个注解@Version先看看源码怎么描述这个注解的

@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface Version {
}

简单来说就是用一个version字段来充当乐观锁的作用。

示例

先来设计实体类

/**
 * Created by xujingfeng on 2017/1/30.
 */
@Entity
@Table(name = "t_student")
public class Student {

    @Id
    @GenericGenerator(name = "PKUUID", strategy = "uuid2")
    @GeneratedValue(generator = "PKUUID")
    @Column(length = 36)
    private String id;

    @Version
    private int version;

    private String name;

    //getter()...
    //setter()...
}

Dao层

/**
 * Created by xujingfeng on 2017/1/30.
 */
public interface StudentDao extends JpaRepository<Student,String>{

    @Query("update Student set name=?1 where id=?2")
    @Modifying
    @Transactional
    int updateNameById(String name,String id);
}

Controller层充当单元测试的作用,通过访问一个requestMapping来触发我们想要测试的方法。

/**
 * Created by xujingfeng on 2017/1/30.
 */
@Controller
public class StudentController {

    @Autowired
    StudentDao studentDao;

    @RequestMapping("student.html")
    @ResponseBody
    public String student(){
        Student student = new Student();
        student.setName("xujingfeng");
        studentDao.save(student);
        return "student";
    }

    @RequestMapping("testVersion.html")
    @ResponseBody
    public String testVersion() throws InterruptedException {
        Student student = studentDao.findOne("6ed16acc-61df-4a66-add9-d17c88b69755");
        student.setName("xuxuan");
        new Thread(new Runnable() {
            @Override
            public void run() {
                studentDao.findOne("6ed16acc-61df-4a66-add9-d17c88b69755");
                student.setName("xuxuanInThread");
                studentDao.save(student);
            }
        }).start();
        Thread.sleep(1000);
        studentDao.save(student);
        return "testVersion";
    }


    @RequestMapping("updateNameById.html")
    @ResponseBody
    public String updateNameById(){
        studentDao.updateNameById("xuxuan2","6ed16acc-61df-4a66-add9-d17c88b69755");
        return "updateNameById";
    }


}

这里面三个方法,主要是我们想用来测试的三个注意点。
第一个方法student.html我们想看看springdata如何对version字段进行增长的。就不贴图了,直接给结论,对于添加了@Version的注解,我们不需要手动去控制,每一次save操作会在原来的基础上+1,如果初始为null,则springdata自动设置其为0。
第二个方法testVersion.html是乐观锁的核心,当多个线程并发访问同一行记录时,添加了@Version乐观锁之后,程序会进行怎么样的控制呢?

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.example.jpa.Student#6ed16acc-61df-4a66-add9-d17c88b69755]

异常信息如上,主线程和新线程获取了同一行记录,并且新线程优先提交了事务,版本号一致,修改成功。等到了主线程再想save提交事务时,便得到一个版本号不一致的异常,那么在项目开发中就应该自己捕获这个异常根据业务内容做对应处理,是重试还是放弃etc…

第三个方法,updateNameById.html是想强调一下,@Query中的update,delete操作是不会触发springdata的相关代理操作的,而是转化为原生sql的方式,所以在项目中使用时也要注意这点。

总结

乐观锁,用在一些敏感业务数据上,而其本身的修饰:乐观,代表的含义便是相信大多数场景下version是一致的。但是从业务角度出发又要保证数据的严格一致性,避免脏读等问题,使用的场景需要斟酌。记得前面一片博文简单介绍了一下行级锁的概念,其实本质上和乐观锁都是想要再数据库层面加锁控制并发,那么什么时候该用乐观锁,行级锁,什么时候得在程序级别加同步锁,又要根据具体的业务场景去判断。找到能够满足自己项目需求的方案,找到性能和可靠性的平衡点,才是一个程序员的价值所在。

在此我向大家推荐一个架构学习交流群。交流学习群号:993070439 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系,还能领取免费的学习资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值