乐趣区

关于springboot:事务注解失效的问题

最近我的项目中遇到并发的问题,所以须要设置锁。因为并发量并不大,所以采纳了乐观锁来锁住数据。然而依照网上设置好了乐观锁之后,运行时却报 no transaction is in progress, 于是查找事务注解生效的起因,并总结几个事务注解会生效的状况。

背景

测试逻辑:
实体新增 test 字段,初始化为 0,调用 save 函数时开始测试,接着调用 filter 函数。

filter 函数用了事务注解,模仿 100 个线程高并发状况下,每个线程都用乐观锁获取同一个 id 的实体,让 test 字段 +1。

dao 层的 findClientById 用了乐观锁,在咱们没有将其提交事务之前,其余线程是不能获取批改的,须要期待。

期待后果:test 字段为 100。

client 实体:

@JsonView(base.class)
    private int test = 0;

service 层:

 public void save(List<Log> logs) throws ParseException {this.filter(logs);
  }

 @Transactional(rollbackFor = Exception.class)
  void filter(List<Log> logs) throws ParseException {for (int i = 0; i < 100 ; i++) {new Thread(() -> {Client client = clientRepository.findClientByIdWithPessimisticLock(9L).get();
        client.setTest(client.getTest() + 1);
        clientRepository.save(client);
      }).start();

dao 层:

     /**
     * 查问时加上乐观锁
     * 在咱们没有将其提交事务之前,其余线程是不能获取批改的,须要期待
     * @param id clientId
     * @return
     */
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @Query("select a from Client a where a.id = :id")
    Optional<Client> findClientByIdWithPessimisticLock(Long id);

测试后果:no transaction is in progress


于是我就搜寻了相干的材料,依照材料

@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Client a where a.id = :id")
@Transational
Optional<Client> findClientByIdWithPessimisticLock(Long id);

做了一个谬误的操作:在 dao 层加上了 @Tansational 事务注解

后果:尽管没有报错,然而 test 后果为 8,不是预期的 100,阐明在高并发下乐观锁没有失效。

猜想:加上该事务注解后,仅仅是在一条 select 语句执行完,事务就会主动 commit,这时候锁曾经去除了,并不是期待的批改完数据后才去除锁。其余线程能够持续获取批改数据。

这种写法不对,咱们须要探清 filter 函数注解生效的真正起因。

那么为什么事务注解会生效呢?咱们先来看一下生效的几种起因。

@Transational 事务注解生效起因

  1. 最常见的起因: 事务办法被外部类调用。
    例如如下代码,在同一个类中调用事务办法的时候,事务注解就会生效。这也是我下面代码事务生效的起因之一。
@Service
public class ServiceImpl implements Service {public void update(Order order) {updateOrder(order);
    }

    @Transactional
    public void updateOrder(Order order) {}}

原理:在之前的文章说过,@Transational 实现原理是 Spring AOP。而 Spring AOP 是通过动静代理的形式实现的。 简略来说就是会生成一个代理类,事务办法会由这个 Spring 生成的代理对象来治理。

只有指标办法由内部调用,能力被 Spring 的事务拦截器拦挡。在同一个类中的两个办法间接调用,不会被 Spring 的事务拦截器拦挡。

为什么会这样子?以给出的代码为例,咱们来看图:

当初 updateOrder 办法曾经被代理类治理。 代理类把原办法包装了起来, 原办法在原类中并没有被加强。

所以,当内部调用 update 办法 (没有事务注解),代理类判断此办法不需进行事务拦挡,间接调用原类。原类再调用 this.updateOrder,此时 this 指向的是原类,并不含有事务拦挡逻辑(事务拦挡逻辑在代理类中),因而注解生效。

然而,如果内部间接调用 updateOrder 办法,是会通过代理类拦挡的,这时候事务注解失效。

能够看出,这种状况下 @Transactional 注解生效的起因在于原类中的 this 并没有被加强 。如果 this 可能指向内部的 Proxy 类,这个问题就不会产生了。

总结:不要在一个类中调用事务办法。能够换种写法,或者思考把事务办法提到一个独自的类中, 由内部调用事务办法

2. 事务办法开启一个新线程

这也是我代码事务生效的起因之一。
例如以下代码:

@Service
public class ServiceImpl implements Service {

    @Transactional
    public void update(Order order) {new Thread(() -> {repository.update()
        }).start();}
}

起因:spring 的事务是通过 LocalThread 来保障线程平安的,事务和以后线程绑定,开启新的线程会让事务生效。

3. 异样没有被抛出或异样类型不对

@Transactional
public void update(Order order) { 
    try {// update order} catch (Exception e) {e.printStackTrace();
    }
}

如上,当异样被捕捉后,并且没有再抛出,那么 update 是不会回滚的

 @Transactional
    public void update(Order order) {
        try {// update order} catch {throw new Exception("更新谬误");
        }
    }

如上,之前文章也说过, 这样事务也是不失效的,因为默认回滚的是:RuntimeException,如果你想触发其余异样的回滚,须要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class)

4. 事务办法不是 public 的
5. 没有被 Spring 治理, 类没有应用 @Service 等注解

解决问题

既然不能在类内调用,曾经事务办法内新开线程,那么能够采纳内部调用的办法进行测试。

controller:

 public void Save() {for (int i = 0; i < 100; i++) {new Thread(() -> {Service.test();
            }).start();}

Service:

@Transactional(rollbackFor = Exception.class)
  public void test() {Client client = clientRepository.findClientByIdWithPessimisticLock(9L).get();
    client.setTest(client.getTest() + 1);
    clientRepository.save(client);
  }

dao:

@Lock(value = LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Client a where a.id = :id")
Optional<Client> findClientByIdWithPessimisticLock(Long id);

测试后果:test 后果为 100,合乎预期,胜利上锁。

大略复制了几行的运行日志展现一下:

DEBUG 3270 --- [Thread-74] org.hibernate.SQL: select client0_.id as id1_0_,
DEBUG 3270 --- [Thread-65] org.hibernate.SQL: update client set delete_at=?,
DEBUG 3270 --- [Thread-75] org.hibernate.SQL: select client0_.id as id1_0_,
DEBUG 3270 --- [Thread-66] org.hibernate.SQL: update client set delete_at=?,
DEBUG 3270 --- [Thread-76] org.hibernate.SQL: select client0_.id as id1_0_, 
DEBUG 3270 --- [Thread-67] org.hibernate.SQL: update client set delete_at=?,
DEBUG 3270 --- [Thread-77] org.hibernate.SQL: select client0_.id as id1_0_, 
DEBUG 3270 --- [Thread-68] org.hibernate.SQL: update client set delete_at=?,

select 语句代表查问实体,update 代表更新。
能够看到,

select 的线程是逐步递增的:74,75,76,

update 的线程也是逐步递增的:65, 66, 67

阐明线程在排队期待操作,期待上一个线程的锁去除后,再执行更新操作。

退出移动版