@Transactional 可以继承,并且使用方法级别优先类级别和最近原则,而且不会将所有的 @Transactional 结合使用。
虽然只读标志被设置为 true,但是事务传播模式被设置为 SUPPORTS,所以不会启动任何事物,因此该方法有效地利用了一个本地(数据库)事务。只读标志只在事务启动时应用。在本例中,因为没有启动任何事务,所以只读标志被忽略。
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Transaction saveReadOnlyAndSupports(Transaction transaction) throws Exception {
Transaction save = transactionRepository.save(transaction);
return save;
}
使用基于 ORM 的框架时,只读标志只是对数据库的一个提示,并且一条基于 ORM 框架的指令(本例中是 Hibernate)是将对象缓存的 flush 模式设置为 NEVER,表示在这个工作单元中,该对象缓存不应与数据库同步。不过,REQUIRED 传播模式会覆盖所有这些内容,允许事务启动并工作,就好像没有设置只读标志一样。
@Transactional 注释的默认传播模式是 REQUIRED。这意味着事务会在不必要的情况下启动。根据使用的数据库,这会引起不必要的共享锁,可能会使数据库中出现死锁的情况。此外,启动和停止事务将消耗不必要的处理时间和资源。总的来说,在使用基于 ORM 的框架时,只读标志基本上毫无用处,在大多数情况下会被忽略。但如果您坚持使用它,请记得将传播模式设置为SUPPORTS,这样就不会启动事务。
但是现在使用save方法时会抛出这个异常: java.lang.ClassCastException:org.hibernate.action.internal.DelayedPostInsertIdentifier cannot be cast to java.math.BigInteger(主键类型);这个可能是 Hibernate的 bug。
只读标记,意味着只能查询,但是查询并不会造成线程失败,所有只读并不具有很大的意义。
注意:不管是使用 Spring Framework,还是使用 EJB,使用 REQUIRES_NEW 事务属性都会得到不好的结果并导致数据损坏和不一致。REQUIRES_NEW 事务属性总是会在启动方法时启动一个新的事务。许多开发人员都错误地使用 REQUIRES_NEW 属性,认为它是确保事务启动的正确方法。
注意:Spring 官方问答中提示,In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted.This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is markedwith @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so youshould not rely on this feature in your initialization code (that is, @PostConstruct).
在代理模式下(默认情况下),只截获通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)不会在运行时导致实际事务,即使被调用的方法被标记为@transactional。此外,代理必须完全初始化才能提供预期的行为,因此在初始化代码中不应依赖此功能(即@postconstruct)。
大致的意思:
在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截。
Double REQUIRED
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于同一事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于同一事务中,两个插入操作都回滚
3,外部方法抛异常 内部方法和外部方法处于同一事务中,两个插入操作都回滚
Double REQUIRES_NEW
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于两个事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
3,外部方法抛异常 内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交
REQUIRED + REQUIRES_NEW
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于两个事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
3,外部方法抛异常 内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交
完整例子:
TransactionRepository.java
package com.xiaomi.springbootbasetest.repository;
import com.xiaomi.springbootbasetest.model.entity.Transaction;
import java.math.BigInteger;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
/**
* @author king
* @date 2019-02-13
*/
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
@Query("update Transaction set message = :message where id = :id")
@Modifying(flushAutomatically = true, clearAutomatically = true)
int update(BigInteger id, String message);
}
TransactionServiceImpl.java
package com.xiaomi.springbootbasetest.service.impl;
import com.xiaomi.springbootbasetest.model.entity.Transaction;
import com.xiaomi.springbootbasetest.repository.TransactionRepository;
import com.xiaomi.springbootbasetest.service.TransactionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @author king
* @date 2019-04-10
*/
@Slf4j
@Service("transactionService")
// @Transactional(noRollbackFor = Exception.class)
public class TransactionServiceImpl implements TransactionService {
private final TransactionRepository transactionRepository;
@Autowired
public TransactionServiceImpl(TransactionRepository transactionRepository) {
this.transactionRepository = transactionRepository;
}
// @Transactional 可以继承,并且使用方法级别优先类级别和最近原则,而且不会将所有的 @Transactional 结合使用
@Override
@Transactional
public Transaction save(Transaction transaction) throws Exception {
Transaction save = transactionRepository.save(transaction);
// int a = 1 / 0;
// throw new Exception("I am an Exception");
return save;
}
/*
虽然只读标志被设置为 true,但是事务传播模式被设置为 SUPPORTS,所以不会启动任何事物,因此该方法有效地利用了一个本地(数据库)事务。
只读标志只在事务启动时应用。在本例中,因为没有启动任何事务,所以只读标志被忽略。
*/
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public Transaction saveReadOnlyAndSupports(Transaction transaction) throws Exception {
Transaction save = transactionRepository.save(transaction);
return save;
}
/*
使用基于 ORM 的框架时,只读标志只是对数据库的一个提示,并且一条基于 ORM 框架的指令(本例中是 Hibernate)是将对象缓存的 flush 模式
设置为 NEVER,表示在这个工作单元中,该对象缓存不应与数据库同步。不过,REQUIRED 传播模式会覆盖所有这些内容,允许事务启动并工作,
就好像没有设置只读标志一样。
@Transactional 注释的默认传播模式是 REQUIRED。这意味着事务会在不必要的情况下启动。根据使用的数据库,这会引起不必要的共享锁,
可能会使数据库中出现死锁的情况。此外,启动和停止事务将消耗不必要的处理时间和资源。总的来说,在使用基于 ORM 的框架时,
只读标志基本上毫无用处,在大多数情况下会被忽略。但如果您坚持使用它,请记得将传播模式设置为 SUPPORTS,这样就不会启动事务。
但是现在使用save方法时会抛出这个异常: java.lang.ClassCastException:
org.hibernate.action.internal.DelayedPostInsertIdentifier cannot be cast to java.math.BigInteger(主键类型);
这个可能是 Hibernate的 bug。
只读标记,意味着只能查询,但是查询并不会造成线程失败,所有只读并不具有很大的意义。
*/
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public Transaction saveReadOnlyAndRequired(Transaction transaction) throws Exception {
Transaction save = transactionRepository.save(transaction);
return save;
}
/*
Double REQUIRED
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于同一事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于同一事务中,两个插入操作都回滚
3,外部方法抛异常 内部方法和外部方法处于同一事务中,两个插入操作都回滚
*/
@Override
@Transactional
public Transaction saveAndRequired(Transaction transaction) throws Exception {
Transaction save = transactionRepository.save(transaction);
// int a = 1 / 0;
return save;
}
@Override
@Transactional
public Transaction saveAndRequired(Transaction transaction1, Transaction transaction2,
TransactionService transactionService) throws Exception {
Transaction save = transactionRepository.save(transaction1);
System.out.println(save);
// 内部方法
save = transactionService.saveAndRequired(transaction2);
System.out.println(save);
/*try {
// 内部方法
save = transactionService.saveAndRequired(transaction2);
System.out.println(save);
} catch (RuntimeException e) {
}*/
int a = 1 / 0;
return save;
}
/*
不管是使用 Spring Framework,还是使用 EJB,使用 REQUIRES_NEW 事务属性都会得到不好的结果并导致数据损坏和不一致。
REQUIRES_NEW 事务属性总是会在启动方法时启动一个新的事务。许多开发人员都错误地使用 REQUIRES_NEW 属性,认为它是确保事务启动的正确方法。
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int updateAndRequiredNew(Transaction transaction) throws Exception {
int result = transactionRepository.update(transaction.getId(), transaction.getMessage());
// result = 1 / 0;
return result;
}
/*
saveAndRequiredNew() 中包含了 updateAndRequiredNew(): 即 后者是前者的一部分,而前者不是后者的一部分;
所以如果后者抛异常即意味着前者也一定会抛异常,但是如果前者抛异常并不意味着后者一定会抛异常。[理想状态]
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Transaction saveAndRequiredNew(Transaction transaction) throws Exception {
Transaction save = transactionRepository.save(transaction);
// int a = 1 / 0;
return save;
}
/*
[实际状态] 这两个嵌套的方法,无论哪里抛出异常,两个方法都会回滚,这样的话,REQUIRES_NEW 与 REQUIRES 岂不是一样一样的啦,伤脑筋呀!!
等等,它好像是指对不同表的操作。
这是类内的方法相互调用,默认 JDK动态代理 不会截获设置事务。
*/
/*
Double REQUIRES_NEW
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于两个事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
3,外部方法抛异常 内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交
*/
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Transaction saveAndRequiredNew(Transaction transaction1, Transaction transaction2,
TransactionService transactionService) throws Exception {
Transaction save = transactionRepository.save(transaction1);
System.out.println(save);
/*
// 内部方法
save = transactionService.saveAndRequiredNew(transaction2);
System.out.println(save);
*/
try {
// Transaction save2 = saveAndRequiredNew(transaction2);
save = transactionService.saveAndRequiredNew(transaction2);
System.out.println(save);
} catch (RuntimeException e) {
}
int a = 1 / 0;
return save;
}
/*
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted.
This means that self-invocation (in effect, a method within the target object calling another method of
the target object) does not lead to an actual transaction at runtime even if the invoked method is marked
with @Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you
should not rely on this feature in your initialization code (that is, @PostConstruct).
在代理模式下(默认情况下),只截获通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)
不会在运行时导致实际事务,即使被调用的方法被标记为@transactional。此外,代理必须完全初始化才能提供预期的行为,
因此在初始化代码中不应依赖此功能(即@postconstruct)。
大致的意思:
在默认的代理模式下,只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。在同一个类中的两个方法直接调用,
是不会被 Spring 的事务拦截器拦截。
*/
/*
REQUIRED + REQUIRES_NEW
1,内部方法抛异常,外部方法不捕获 内部方法和外部方法处于两个事务中,两个插入操作都回滚
2,内部方法抛异常,外部方法捕获吃掉 内部方法和外部方法处于两个事务中,外部方法提交,内部方法回滚
3,外部方法抛异常 内部方法和外部方法处于两个事务中,外部方法回滚,内部方法提交
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public Transaction saveAndRequiredAndRequiredNew(Transaction transaction1, Transaction transaction2,
TransactionService transactionService) throws Exception {
Transaction save = transactionRepository.save(transaction1);
System.out.println(save);
/*
// 内部方法
save = transactionService.saveAndRequiredNew(transaction2);
System.out.println(save);*/
try {
// 注意这一行代码,是通过外部传入的 transactionService 对象调用的。
save = transactionService.saveAndRequiredNew(transaction2);
System.out.println(save);
} catch (RuntimeException e) {
}
int a = 1 / 0;
return save;
}
}
TransactionServiceTest.java
package com.xiaomi.springbootbasetest.service;
import com.xiaomi.springbootbasetest.model.entity.Transaction;
import java.math.BigInteger;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author king
* @date 2019-04-10
*/
@RunWith(SpringRunner.class)
@SpringBootTest
/*
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ComponentScan("com.xiaomi")
*/ public class TransactionServiceTest {
@Autowired
private TransactionService transactionService;
@Test
public void save() throws Exception {
Transaction transaction = Transaction.builder().type("Exception").message("普通异常测试").build();
Transaction save = transactionService.save(transaction);
Assertions.assertThat(save).isNotNull();
System.out.println(save);
}
@Test
public void saveReadOnlyAndSupports() throws Exception {
Transaction transaction = Transaction.builder().type("Exception").message("只读和Supports异常测试").build();
Transaction save = transactionService.saveReadOnlyAndSupports(transaction);
Assertions.assertThat(save).isNotNull();
System.out.println(save);
}
@Test
public void saveReadOnlyAndRequired() throws Exception {
Transaction transaction = Transaction.builder().type("Exception").message("只读和Required异常测试").build();
Transaction save = transactionService.saveReadOnlyAndRequired(transaction);
Assertions.assertThat(save).isNotNull();
System.out.println(save);
}
@Test
public void saveAndRequired2() throws Exception {
Transaction transaction1 = Transaction.builder().type("Exception").message("Required异常测试1").build();
Transaction transaction2 = Transaction.builder().type("Exception").message("Required异常测试2").build();
Transaction save = transactionService.saveAndRequired(transaction1, transaction2, transactionService);
Assertions.assertThat(save).isNotNull();
}
@Test
public void updateAndRequiredNew() throws Exception {
Transaction transaction =
Transaction.builder().id(BigInteger.valueOf(1000)).type("Exception").message("RequiredNew异常update测试")
.build();
int result = transactionService.updateAndRequiredNew(transaction);
Assertions.assertThat(result).isGreaterThan(0);
System.out.println(result);
}
@Test
public void saveAndRequiredNew() throws Exception {
Transaction transaction = Transaction.builder().type("Exception").message("RequiredNew异常测试").build();
Transaction save = transactionService.saveAndRequiredNew(transaction);
Assertions.assertThat(save).isNotNull();
System.out.println(save);
}
@Test
public void saveAndRequiredNew2() throws Exception {
Transaction transaction1 = Transaction.builder().type("Exception").message("RequiredNew异常测试1").build();
Transaction transaction2 = Transaction.builder().type("Exception").message("RequiredNew异常测试2").build();
Transaction save = transactionService.saveAndRequiredNew(transaction1, transaction2, transactionService);
Assertions.assertThat(save).isNotNull();
}
@Test
public void saveAndRequiredAndRequiredNew() throws Exception {
Transaction transaction1 =
Transaction.builder().type("Exception").message("RequiredAndRequiredNew异常测试1").build();
Transaction transaction2 =
Transaction.builder().type("Exception").message("RequiredAndRequiredNew异常测试2").build();
Transaction save = transactionService.saveAndRequiredAndRequiredNew(transaction1, transaction2,
transactionService);
Assertions.assertThat(save).isNotNull();
}
}
网友评论