JPA 加锁机制及@Version版本控制方式

编辑: admin 分类: java 发布时间: 2021-12-04 来源:互联网
目录
  • 一 简述悲观锁的用法
    • 1.1 EntityManager 用法
  • 二 乐观锁的详细用法
    • 实体类(注意其中的@Version注解)
  • 总结

    JPA的加锁机制有两种,乐观锁和悲观锁。

    乐观锁:

    乐观锁的特点在于认为数据冲突或者更新丢失等情况是很少发生的.当发生的时候,抛出异常和回滚就足够解决问题.

    悲观锁:

    悲观锁的逻辑在于认为每次数据操作都很有可能发生冲突,所以一开始就获得记录的锁,再进行记录的操作是解决问题的优先选择.

    一 简述悲观锁的用法

    悲观锁通常是SQL级别的,通过读写时先拿到锁实现,在SQL语句中就会有体现.

    1.1 EntityManager 用法

        return em.createQuery(sql 语句).setLockMode(LockModeType.NONE).getResultList();
        //分解写法大概是:
        Query query = getSession().createQuery(hql);
        query.setLockMode(LockModeType.NONE);

    EntityManager 是一个辅助类,createQuery后返回的就是一个Query对象,然后通过

    setLockMode设置锁的级别即可.

    LockModeType 类型 解释 LockMode.READ 事务的隔离级别是Repeatable Read或Serializable时,请求读取数据库记录时自动获得 LockMode.WRITE 请求插入或更新数据库记录时自动获得 LockMode.OPTIMISTIC 乐观锁 LockMode.OPTIMISTIC_FORCE_INCREMENT 乐观锁,通过version控制 LockMode.PESSIMISTIC_READ 与LockMode.PESSIMISTIC_WRITE相同 LockMode.PESSIMISTIC_WRITE 事务开始即获得数据库的锁 LockMode.PESSIMISTIC_FORCE_INCREMENT 事务开始即设置version LockMode.NONE 取消任何锁,如事务结束后的所有对象,或执行了Session的update()、

    二 乐观锁的详细用法

    乐观锁本篇的主要内容

    实体类是关键 , 乐观锁常用方法是通过version来控制 ,

    • 数据库对应的表中需要有一个字段(名字随意),字段类型设置成BigInt即可
    • 业务不对该字段进行控制,字段的控制交由系统处理
    • 每一次修改都会导致version递增
    • 当出现同时获得该记录的对象且均需要修改时,当第一个已经提交事务,version字段发生改变,后面提交的事务发现version版本不对,则无法提交,抛出异常

    实体类(注意其中的@Version注解)

    @Entity
    public class User {
        @Id
        @GeneratedValue
        private Long id;
        private String username;
        private String userdesc;
        @Version
        private Long version;
        public User() {
        }
        public User(String username, String userdesc) {
            this.username = username;
            this.userdesc = userdesc;
        }
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getUsername() {
            return username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getUserDesc() {
            return userdesc;
        }
        public void setUserDesc(String userdesc) {
            this.userdesc = userdesc;
        }
        public Long getVersion() {
            return version;
        }
        public void setVersion(Long version) {
            this.version = version;
        }
    }
    

    controller中通过sleep将线程沉睡,测试事务的提交性

    @RestController
    public class UserController {
        private Logger logger = LoggerFactory.getLogger(getClass());
        @Autowired
        UserService userService;
        @PostMapping("/changeone")
        @Transactional
        public String changeone() {
            User user = userService.findUser("gang");
            try {
                logger.info("修改1 before:user--{}--Versdion:{}", user.getUserDesc(), user.getVersion());
                Thread.sleep(25000);
                user.setUserDesc("修改1");
                logger.info("修改1 :user--{}--version:{}", user.getUserDesc(), user.getVersion());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                logger.info("eeeeeeeeeeeeee");
                e.printStackTrace();
            }
            return "true";
        }
        @PostMapping("/changetwo")
        @Transactional
        public String changetwo() {
            User user = userService.findUser("gang");
            try {
                logger.info("修改2 before:user--{}--version:{}", user.getUserDesc(), user.getVersion());
                Thread.sleep(30000);
                user.setUserDesc("修改2");
                logger.info("修改2:user--{}--version:{}", user.getUserDesc(), user.getVersion());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Exception e) {
                logger.info("eeeeeeeeeeeeee");
                e.printStackTrace();
            }
            return "true";
        }
        @PostMapping("/changethree")
        @Transactional
        public String changethree() {
            User user = userService.findUser("gang");
            logger.info("修改3 before:user--{}--version:{}", user.getUserDesc(), user.getVersion());
            user.setUserDesc("修改3");
            logger.info("修改3 :user--{}--version:{}", user.getUserDesc(), user.getVersion());
            return "true";
        }
        @PostMapping("/newuser")
        @Transactional
        public String newuser() {
            logger.info("save user");
            User user = new User();
            user.setUserDesc("第一次创建");
            user.setUsername("gang");
            userService.saveUser(user);
            return "true";
        }
    }
    

    以及service及repository

    @Service
    public class UserService {
        @Autowired
        UserRepository userRepository;
        public User findUser(String username){
            return userRepository.findByUsername(username);
        }
        public void saveUser(User user){
            userRepository.save(user);
        }
    }
    UserRepository 
    public interface UserRepository extends JpaRepository<User,Long> {
        User findByUsername(String username);
    }
    

    总结

    使用很简单,version是自动增长的,唯一的缺点是抛出的异常不易捕获,捕获的方法:

        @Resource
        private UserTransaction rtc;
         try {
                rtc.begin();
                User user = userService.findUser("gang");
                user .setDesc("异常捕获");
                 rtc.commit();
            } catch (OptimisticLockException e) {
                throw new OptimisticLockException ();
            } catch (Exception e) {
                throw new Exception ();
            }
    

    注意其中的 rtc.begin(); 以及 rtc.commit();

    不同于@Transaction,这种是手动的提交方法

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持自由互联。

    【文章出处:台湾服务器 转载请保留连接】