前面spring-事务源码解析(一)和spring-事务源码解析(二)两篇文章分别介绍了spring容器如何对bean进行事务代理和具体事务代理逻辑以及如何和我们自己使用的JDBC框架(以JdbcTemplate为例)一起集成的,今天来说一下spring事务的传播机制,这点无论是在面试中还是自己工作中都是很重要的部分。
1. spring事务提供的传播机制枚举
/**
* 如果有事务,就加入事务,没有的话就新建一个(默认)
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
*支持当前事务,如果当前没有事务,就以非事务方式执行
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* 使用当前的事务,如果当前没有事务,就抛异常
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* 不管是否存在事务,都创建一个新的事务,原来的事务挂起,新的执行完毕,继续执行老的事务
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 以非事务方式执行,如果当前存在事务,就把当前事务挂起
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
*以非事务方式执行,如果当前存在事务,则抛异常(与MANDATORY相反)
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* 如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行与REQUIRED类似的操作
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
2.测试
@Repository
public class UserDaompl implements IUserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
@Override
public int insertRequired(String name) {
return jdbcTemplate.update("insert into user(name) values(?)", name);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
@Override
public int insertRequiresNew(String name) {
return jdbcTemplate.update("insert into user(name) values(?)", name);
}
@Transactional(propagation=Propagation.NESTED)
@Override
public int insertNested(String name) {
return jdbcTemplate.update("insert into user(name) values(?)", name);
}
}
@Repository
public class User2DaoImpl implements IUser2Dao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int insertException(String name) {
jdbcTemplate.update("insert into user2(name) values(?)", name);
throw new IllegalArgumentException();
}
@Transactional
@Override
public int insertRequired(String name) {
return jdbcTemplate.update("insert into user2(name) values(?)", name);
}
@Transactional
@Override
public int insertRequiredException(String name) {
jdbcTemplate.update("insert into user2(name) values(?)", name);
throw new IllegalArgumentException();
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
@Override
public int insertRequiresNew(String name) {
return jdbcTemplate.update("insert into user2(name) values(?)", name);
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
@Override
public int insertRequiresNewException(String name) {
jdbcTemplate.update("insert into user2(name) values(?)", name);
throw new IllegalArgumentException();
}
@Transactional(propagation=Propagation.NESTED)
@Override
public int insertNested(String name) {
return jdbcTemplate.update("insert into user2(name) values(?)", name);
}
@Transactional(propagation=Propagation.NESTED)
@Override
public int insertNestedException(String name) {
jdbcTemplate.update("insert into user2(name) values(?)", name);
throw new IllegalArgumentException();
}
}
@Service
public class UserServiceImpl implements IUserService{
@Autowired
private IUserDao userDao;
@Autowired
private IUser2Dao user2Dao;
/********************** required *******************************/
@Transactional
@Override
public void notTransaction__required_not_transaction_exception() {
// 会回滚
user2Dao.insertException("李四");
// 不会回滚
// try {
// user2Dao.insertException("李四");
// } catch (Exception e) {
// System.out.println("方法回滚");
// }
}
/**
* 张三、李四都可以插入
* 外围方法未开启事务,内部两个方法都在自己的事务中独立运行,外围方法异常不影响内部方法独立的事务
*/
@Override
public void notTransaction_exception_required_required() {
userDao.insertRequired("张三");
user2Dao.insertRequired("李四");
throw new IllegalArgumentException();
}
/**
* 张三可以插入, 李四不能插入
* 外围方法没有事务,内部方法都在自己的事务中独立运行,所以方法2抛出异常只会回滚自己的事务,不会影响方法1的事务
*/
@Override
public void notTransaction_required_required_exception() {
userDao.insertRequired("张三");
user2Dao.insertRequiredException("李四");
}
/**
* 张三、李四都未插入
* 外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法事务也会回滚
*/
@Transactional
@Override
public void transaction_exception_required_required() {
userDao.insertRequired("张三");
user2Dao.insertRequired("李四");
throw new IllegalArgumentException();
}
/**
* 张三,李四均为插入
* 外部方法开启事务,内部方法加入外部方法事务,内部方法抛出异常,外部方法感知到异常导致整体事务回滚
*/
@Transactional
@Override
public void transaction_required_required_exception() {
userDao.insertRequired("张三");
user2Dao.insertRequiredException("李四");
}
/**
* 张三,李四均为插入
* 外部方法开启事务,内部方法加入外部方法事务,内部方法抛出异常会标记当前事务为rollback-only,
* 即使异常在外部方法中被catch了,外部方法事务不会检测到异常,但会检测到rollback-only,从而导致整体事务回滚
*/
@Transactional
@Override
public void transaction_required_required_exception_try() {
userDao.insertRequired("张三");
try {
user2Dao.insertRequiredException("李四");
} catch (Exception e) {
System.out.println("方法回滚");
}
}
/********************** requires_new *******************************/
/**
* 张三,李四均插入
* 外部方法没有事务,内部方法都在自己的事务中独立运行,外部方法抛出异常不会影响内部方法
*/
@Override
public void notTransaction_exception_requiresNew_requiresNew() {
userDao.insertRequiresNew("张三");
user2Dao.insertRequiresNew("李四");
throw new IllegalArgumentException();
}
/**
* 张三插入,李四未插入
* 外部方法没有开始事务,内部方法运行在自己独立事务内,插入李四方法抛出异常回滚,其它事务不受影响
*/
@Override
public void notTransaction_requiresNew_requiresNew_exception() {
userDao.insertRequiresNew("张三");
user2Dao.insertRequiresNewException("李四");
}
/**
* 张三未插入,李四插入,王五插入
* 外部方法开启事务,插入张三方法使用外部方法事务,插入李四和王五的方法都运行在各自独立事务内,
* 外部方法抛异常回滚只会回滚同一事务的方法,所以,插入张三的方法会一同回滚
*/
@Transactional
@Override
public void transaction_exception_required_requiresNew_requiresNew() {
userDao.insertRequired("张三");
user2Dao.insertRequiresNew("李四");
user2Dao.insertRequiresNew("王五");
throw new IllegalArgumentException();
}
/**
* 张三未插入,李四插入,王五未插入
* 外部方法开启事务,插入张三方法使用外部方法事务,插入李四和王五的方法都运行在各自独立事务内,
* 插入王五方法抛出异常,首先自己的事务会回滚,随后,异常继续抛出被外部方法感知,外部方法事务会被回滚,
* 古插入张三的方法也会随之回滚
*/
@Transactional
@Override
public void transaction_required_requiresNew_requiresNew_exception() {
userDao.insertRequired("张三");
user2Dao.insertRequiresNew("李四");
user2Dao.insertRequiresNewException("王五");
}
/**
* 张三插入,李四插入,王五未插入
* 外部方法开启事务,插入张三方法和外部方法共用同一个事务,插入李四和王五的方法都运行在各自独立事务内,
* 插入王五方法抛出异常,首先自己的事务会回滚,随后,异常外部方法catch,不会被外围事务感知到,
* 所以,外部方法事务不会回滚,插入张三方法会插入成功
*/
@Transactional
@Override
public void transaction_required_requiresNew_requiresNew_exception_try() {
userDao.insertRequired("张三");
user2Dao.insertRequiresNew("李四");
try {
user2Dao.insertRequiresNewException("王五");
} catch (Exception e) {
System.out.println("事务回滚");
}
}
/********************** nested *******************************/
/**
* 张三,李四均插入
* 外围方法未开启事务,内部方法均运行在各自独立事务中,外部方法异常不影响内部方法事务
*/
@Override
public void notTransaction_exception_nested_nested() {
userDao.insertNested("张三");
user2Dao.insertNested("李四");
throw new IllegalArgumentException();
}
/**
* 张三插入,李四未插入
* 外部方法未开启事务,内部方法均运行在各自独立事务中,插入李四方法抛出异常,只会让自己事务回滚,
* 虽然向外部方法抛出了异常,但不会影响插入张三方法事务
*/
@Override
public void notTransaction_nested_nested_exception() {
userDao.insertNested("张三");
user2Dao.insertNestedException("李四");
}
/**
* 张三,李四均未插入
* 外部方法开启事务,内部方法事务是外部事务的子事务(savePoint),外部方法事务回滚,子事务也会随之回滚
*/
@Transactional
@Override
public void transaction_exception_nested_nested() {
userDao.insertNested("张三");
user2Dao.insertNested("李四");
throw new IllegalArgumentException();
}
/**
* 张三,李四均未插入
* 外围方法开启事务,内部方法事务是外部事务的子事务,内部方法抛异常回滚,且外部事务感知到异常会导致整体事务的回滚
*/
@Transactional
@Override
public void transaction_nested_nested_exception() {
userDao.insertNested("张三");
user2Dao.insertNestedException("李四");
}
/**
* 张三插入,李四未插入
* 外部方法开启事务,内部事务为外部事务子事务,插入李四方法抛异常回滚,且异常被外部方法catch,不会被外部事务感知,所以整体事务不会回滚
*/
@Transactional
@Override
public void transaction_nested_nested_exception_try() {
userDao.insertNested("张三");
try {
user2Dao.insertNestedException("李四");
} catch (Exception e) {
System.out.println("事务回滚");
}
}
/**
* 张三插入,李四插入(事务回滚失效)
* 外部方法开启事务,插入李四方法为外部事务子事务,这个方法抛出异常,按道理是应该回滚的,但实际并非如此,和transaction_nested_nested_exception_try比较一下
*/
@Transactional
@Override
public void transaction_fail() {
user2Dao.insertRequired("张三");
try {
user2Dao.insertNestedException("李四");
} catch (Exception e) {
System.out.println("事务回滚");
}
}
/**
* 张三插入,李四未插入(事务回滚生效)
* 根本原因和事务没有关系,是动态代理导致的
*/
@Transactional
@Override
public void transaction_success() {
user2Dao.insertRequired("张三");
try {
// 该用法需要@EnableAspectJAutoProxy(exposeProxy=true)注解支持
((IUserService)AopContext.currentProxy()).insertNestedException("李四");
} catch (Exception e) {
System.out.println("事务回滚");
}
}
@Transactional(propagation=Propagation.NESTED)
@Override
public int insertNestedException(String name) {
jdbcTemplate.update("insert into user2(name) values(?)", name);
throw new IllegalArgumentException();
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTests {
@Autowired
private IUserService userService;
@Before
public void setUp() {}
@Test
public void test() {
userService.notTransaction_exception_required_required();
}
}
上面只对required,requires_new,nested三种传播机制进行了测试,测试结果标记在了对应的方法上,其它的传播机制,感兴趣的同学可以自己动手测试