58.安心技术梳理 - 乐观锁注解@Version两个包区别(javax.persistence.Version)和(tk.mybatis.mapper.annotation.Version)

一、javax.persistence.Version

数据库并发操作时,为了保证数据的正确性,经常要对数据加锁,加锁有两种方式:悲观锁、乐观锁

悲观锁:把所需要的数据全部加锁,不允许其他事务对数据做修改

update xxx where xxxx for update

乐观锁:对数据进行版本校验,如果版本不一致,则操作数据失败

update xxx,version+1 where xxxx and version=x

在jpa中,@Version注解,可以实现乐观锁功能

实体类Account,version属性上加@Version注解

package com.xhx.springboot.entity;

import javax.persistence.Entity;

import javax.persistence.Id;

import javax.persistence.Version;

/**

* @author xuhaixing

* @date 2018/4/28 10:29

*/

@Entity

public class Account {

@Id

private int id;

private String name;

private Double money;

@Version

private int version;

public int getVersion() {

return version;

}

public void setVersion(int version) {

this.version = version;

}

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public Double getMoney() {

return money;

}

public void setMoney(Double money) {

this.money = money;

}

}

在更新数据时,需要用jpa自己实现的save方法

S save(S var1);

Iterable saveAll(Iterable var1);

如果是自己写的update方法,下面这样,是不生效的

@Repository

public interface AccountDao extends JpaRepository {

@Modifying

@Query("update Account set name=:name, money=:money where id=:id")

int updateAccount(@Param("id") int id,@Param("name") String name, @Param("money") double money);

}

数据库数据如下:

我们更新id是10的数据,数据库中版本是0,我们设置版本1

Account account = new Account();

account.setId(10);

account.setName("eeee");

account.setMoney(7999.0);

account.setVersion(1);

accountController.update(account);

报如下错误:

org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [com.xhx.springboot.entity.Account] with identifier [10]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.xhx.springboot.entity.Account#10]

at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:298)

at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)

at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)

把版本号改成0,再更新,数据库中执行如下语句,更新成功

再看数据库,版本号+1了

自己实现可以这样:

@Modifying

@Query("update Account set name=:name, money=:money,version=:version+1 where id=:id and version=:version")

int updateAccountByVersion(@Param("id") int id,@Param("name") String name, @Param("money") double money,@Param("version") int version);

service中加判断,抛异常,这样就自己通过数据库实现了乐观锁

@Transactional(rollbackFor = Exception.class)

public int updateAccountByVersion(Account account){

int i =accountDao.updateAccountByVersion(account.getId(),account.getName(),account.getMoney(),account.getVersion());

if(i==0){

throw new ObjectOptimisticLockingFailureException("更新account失败",new Exception());

}

return i;

}

二、tk.mybatis.mapper.annotation.Version

想要使用乐观锁,只需要在实体中,给乐观锁字段增加 @tk.mybatis.mapper.annotation.Version 注解。

例如:

public class User {
     private Long id;
      private String name;
    //...
    @Version
      private Integer version;
      //setter and getter
}
@Version 注解有一个 nextVersion 属性,默认值为默认的实现,默认实现如下:

package tk.mybatis.mapper.version;

import java.sql.Timestamp;

/**
 * @author liuzh
 * @since 3.5.0
 */
public class DefaultNextVersion implements NextVersion {

    @Override
    public Object nextVersion(Object current) throws VersionException {
        if (current == null) {
            throw new VersionException("当前版本号为空!");
        }
        if (current instanceof Integer) {
            return (Integer) current + 1;
        } else if (current instanceof Long) {
            return (Long) current + 1L;
        } else if (current instanceof Timestamp) {
            return new Timestamp(System.currentTimeMillis());
        } else {
            throw new VersionException("默认的 NextVersion 只支持 Integer, Long" +
                    " 和 java.sql.Timestamp 类型的版本号,如果有需要请自行扩展!");
        }
    }

}
默认实现支持 Integer, Long 和 java.sql.Timestamp ,如果默认实现不能满足自己的需要,可以实现自己的方法,在配置注解时指定自己的实现即可。

支持的方法
delete
deleteByPrimaryKey
updateByPrimaryKey
updateByPrimaryKeySelective
updateByExample
updateByExampleSelective
这些方法在执行时会更新乐观锁字段的值或者使用乐观锁的值作为查询条件。

需要注意的地方
在使用乐观锁时,由于通用 Mapper 是内置的实现,不是通过 拦截器 方式实现的,因此当执行上面支持的方法时,如果版本不一致,那么执行结果影响的行数可能就是 0。这种情况下也不会报错!

所以在 Java6,7中使用时,你需要自己在调用方法后进行判断是否执行成功。

在 Java8+ 中,可以通过默认方法来增加能够自动报错(抛异常)的方法,例如:

public interface MyMapper<T> extends Mapper<T> {
  
  default int deleteWithVersion(T t){
    int result = delete(t);
    if(result == 0){
      throw new RuntimeException("删除失败!");
    }
    return result;
  }
  
  default int updateByPrimaryKeyWithVersion(Object t){
    int result = updateByPrimaryKey(t);
    if(result == 0){
      throw new RuntimeException("更新失败!");
    }
    return result;
  }
  //...
}
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值