ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Spring Data JPA 从入门到精通~@Version处理乐观锁的问题

2022-01-09 17:31:01  阅读:208  来源: 互联网

标签:version JPA Spring entity 乐观 Version public name


@Version 处理乐观锁的问题

@Version 乐观锁介绍

我们在研究 Auditing 的时候,发现了一个有趣的注解 @Version,源码如下:

package org.springframework.data.annotation;
/**
 * Demarcates a property to be used as version field to implement optimistic locking on entities.
 */
@Retention(RUNTIME)
@Target(value = { FIELD, METHOD, ANNOTATION_TYPE })
public @interface Version {}

发现它帮我们处理了乐观锁的问题,什么是乐观锁,还有线程的安全性,在另外一本书《Java 并发编程从入门到精通》里面,或者看作者的另外一篇 Chat:Java 多线程与并发编程 · Java 工程师必知必会,作者做了深入的探讨。

对于数据来说,简单理解:在数据库并发操作时,为了保证数据的正确性,我们会做一些并发处理,主要就是加锁。在加锁的选择上,常见有两种方式:悲观锁和乐观锁。

  • 悲观锁:简单的理解就是把需要的数据全部加锁,在事务提交之前,这些数据全部不可读取和修改。
  • 乐观锁:使用对单条数据进行版本校验和比较,来对保证本次的更新是最新的,否则就失败,效率要高很多。在实际工作中,乐观锁不止在数据库层面,其实我们在做分布式系统的时候,为了实现分布式系统的数据一致性,分布式事物的一种做法就是乐观锁。

数据库操作举例说明

悲观锁的做法:

select * from user where id=1 for update;
update user  set name='jack'  where id=1;

通过使用 for update 给这条语句加锁,如果事务没有提交,其他任何读取和修改,都得排队等待。在代码中,我们加事务的 Java 方法就会自然的形成了一个锁。

乐观锁的做法:

select uid,name,version from user where id=1;
update user set name='jack', version=version+1 where id=1 and version=1

假设本次查询 version=1,在更新操作时,带上这次查出来的 Version,这样只有和我们上次版本一样的时候才会更新,就不会出现互相覆盖的问题,保证了数据的原子性。

@Version 用法

在没有 @Version 之前,我们都是自己手动维护这个 Version 的,这样很有可能做什么操作的时候给忘掉。或者是我们自己底层做框架,用 AOP 的思路做拦截底层维护这个 Version 的值。而 Spring Data JPA 的 @Version 就是通过 AOP 机制,帮我们动态维护这个 Version,从而更优雅的实现乐观锁。

(1)实体上的 Version 字段加上 @Version 注解即可。

我们对上面的实体 UserCustomerEntity 改进如下:

@Entity
@Table(name = "user_customer", schema = "test", catalog = "")
public class UserCustomerEntity extends AbstractAuditable {
   //新增控制乐观锁的字段。并且加上@Version注解
   @Version
   @Column(name = "version", nullable = true)
   private Long version;
......
}

(2)实际调用

userCustomerRepository.save(new UserCustomerEntity("1","Jack"));
UserCustomerEntity uc= userCustomerRepository.findOne(1);
uc.setCustomerName("Jack.Zhang");
userCustomerRepository.save(uc);

我们会发现 Insert 和 Update 的 SQL 语句都会带上 Version 的操作。当乐观锁更新失败的时候,会抛出异常 org.springframework.orm.ObjectOptimisticLockingFailureException。

实现原理关键代码

(1)SimpleJpaRepository.class 里面的 save 方法如下:

public <S extends T> S save(S entity) {
   if (entityInformation.isNew(entity)) {
      em.persist(entity);
      return entity;
   } else {
      return em.merge(entity);
   }
}

(2)如果我们在此处设置一个 debug 断点的话,我们一步一步往下面走会发现进入 JpaMetamodelEntityInformation.class 的关键代码如下:

    @Override
    public boolean isNew(T entity) {
        if (!versionAttribute.isPresent()
                || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
            return super.isNew(entity);
        }
        BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
        return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
    }

所以到这里,可以看出当我们更新的时候,若实体对象上面有 @Version 注解,那么就一定要带上 version,如果没带上 version 字段的值,只有 ID 字段的值,系统也会认为是新增。相反,如果我们没有 @Version 注解的字段,那么就会以 @ID 字段来判断是否是新增。其实这里我们也明白,省去了传统都需要我们自己去实现的 saveOrUpdate 方法。

(3)其实我们多看看代码,多 debug 几次就会发现,也可以在 @Entity 的类里面覆盖掉 isNew() 方法,这样可以实现自己的 isNew 的判断逻辑。

@Entity
@Table(name = "user")
public class UserEntity  implements Persistable {
    @Transient   //这个注解表明这个字段不是持久化的
    @JsonIgnore //json显示的时候我们也可忽略这个字段
    @Override
    public boolean isNew() {
        return getId() == null;
    }
    ....
}

标签:version,JPA,Spring,entity,乐观,Version,public,name
来源: https://blog.csdn.net/gqltt/article/details/122396209

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有