赞
踩
https://zhuanlan.zhihu.com/p/141102728
事务管理是应用系统开发中必不可少的一部分,而Spring则为事务管理提供了丰富的功能支持.
在讲解其实现原理前,我们先来看看使用Spring的事务管理机制给我们带来的好处:它极大减少了样板式代码,提高了代码的清晰性和可维护性.我这样讲你可能没什么感觉,下面我通过原生的jdbc事务处理代码例子,以及Spring的事务处理代码例子来突出说明这个好处.
先建立三张表,表字段如下:
需求是要往这三张表保存数据,保存顺序是country,city,category.其中前两张表要求是原子操作,后一张表是另一个原子操作,即后一张表操作失败不需回滚前两张表的数据.
为了实现这个要求,原生的jdbc事务处理代码实现是这样的(可以看到大量的样板式代码):
- @Slf4j
- @Service
- public class AddressServiceImpl implements AddressService {
-
- @Autowired
- private DataSource dataSource;
-
- @SneakyThrows
- @Override
- public String transactionOriginalResearch() {
- try (Connection conn = dataSource.getConnection()) {
- conn.setAutoCommit(false);
-
- short countryId;
- PreparedStatement preparedStatement = null;
- ResultSet resultSet = null;
- try {
- // 其他业务逻辑处理
- // ......
- preparedStatement = conn.prepareStatement("insert into country(country) values (?)", Statement.RETURN_GENERATED_KEYS);
- preparedStatement.setString(1, "中国");
- preparedStatement.executeUpdate();
- resultSet = preparedStatement.getGeneratedKeys();
- resultSet.next();
- countryId = resultSet.getShort(1);
- } catch (Exception e) {
- conn.rollback();
- throw new RuntimeException(e);
- } finally {
- if (preparedStatement != null) {
- preparedStatement.close();
- }
- if (resultSet != null) {
- resultSet.close();
- }
- }
-
- short cityId;
- try {
- // 其他业务逻辑处理
- // ......
- preparedStatement = conn.prepareStatement("insert into city(city, country_id) values (?,?)", Statement.RETURN_GENERATED_KEYS);
- preparedStatement.setString(1, "北京");
- preparedStatement.setShort(2, countryId);
- preparedStatement.executeUpdate();
- resultSet = preparedStatement.getGeneratedKeys();
- resultSet.next();
- cityId = resultSet.getShort(1);
- } catch (Exception e) {
- conn.rollback();
- throw new RuntimeException(e);
- } finally {
- if (preparedStatement != null) {
- preparedStatement.close();
- }
- if (resultSet != null) {
- resultSet.close();
- }
- }
- // country表和city表均保存成功,此处设置个Savepoint(保存点)
- Savepoint savepointCity = conn.setSavepoint("city");
-
- int affect;
- try {
- // 其他业务逻辑处理
- // ......
- preparedStatement = conn.prepareStatement("insert into category(name) values (?)");
- preparedStatement.setString(1, "分类名称");
- affect = preparedStatement.executeUpdate();
- if (true) {
- throw new RuntimeException("模拟抛出异常");
- }
- conn.commit();
- } catch (Exception e) {
- conn.rollback(savepointCity);
- throw new RuntimeException(e);
- } finally {
- if (preparedStatement != null) {
- preparedStatement.close();
- }
- }
- return countryId + " " + cityId + " " + affect;
- }
- }
-
- }
可以看到,事务包含了复杂的语句,我们通过设置Savepoint(保存点)回滚到了事务中某个特殊的点,而不是回滚整个事务.
再来看使用Spring的声明式事务的代码实现:
- @Slf4j
- @Service
- public class AddressServiceImpl implements AddressService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- @Autowired
- private AddressHelper addressHelper;
-
- @Override
- @Transactional(timeout = 6000, rollbackFor = Exception.class)
- public String transactionResearch() {
- String res = addressHelper.save();
- // 其他业务逻辑处理
- // ......
- int affect = jdbcTemplate.update("insert into category(name) values (?)", "分类名称");
- if (true) {
- throw new RuntimeException("模拟抛出异常");
- }
- return res + " " + affect;
- }
-
- }
-
- @Component
- public class AddressHelper {
-
- @Autowired
- private JdbcTemplate jdbcTemplate;
-
- @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
- public String save() {
- // 其他业务逻辑处理
- // ......
- GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
- jdbcTemplate.update(con -> {
- PreparedStatement preparedStatement = con.prepareStatement("insert into country(country) values (?)", Statement.RETURN_GENERATED_KEYS);
- preparedStatement.setString(1, "中国");
- return preparedStatement;
- }, keyHolder);
- short countryId = keyHolder.getKey().shortValue();
-
- // 其他业务逻辑处理
- // ......
- GeneratedKeyHolder keyHolder1 = new GeneratedKeyHolder();
- jdbcTemplate.update(con -> {
- PreparedStatement preparedStatement = con.prepareStatement("insert into city(city,country_id) values (?,?)", Statement.RETURN_GENERATED_KEYS);
- preparedStatement.setString(1, "北京");
- preparedStatement.setInt(2, countryId);
- return preparedStatement;
- }, keyHolder1);
- short cityId = keyHolder1.getKey().shortValue();
-
- return countryId + " " + cityId;
- }
-
- }
注意AddressHelper上的事务属性设置Propagation.REQUIRES_NEW.可以看到仅仅简单加上@Transactional注解,Spring就替我们完成了所有的事务操作.这样是不是就深刻感受到Spring带来的好处了.
另外Spring也提供了编程式事务,通过使用TransactionTemplate,这里不再赘述.
使用@Transactional有一些需要注意的地方:
好,下面开始进入主题,我们知道@Transactional注解要生效的话,需配置@EnableTransactionManagement,不过如果是使用SpringBoot的话,就可以不需要了:
- @SpringBootApplication
- @EnableTransactionManagement // 这行注解其实可以不需要,在TransactionAutoConfiguration自动配置类里已经带有此注解
- public class SpringTransactionalApplication {
-
- public static void main(String[] args) {
- SpringApplication.run(SpringTransactionalApplication.class, args);
- }
-
- }
TransactionAutoConfiguration自动配置类定义了很多与事务处理相关的bean,其中与@Transactional注解息息相关的是这个类TransactionInterceptor.
每个带有@Transactional注解的方法都会创建一个切面,所有的事务处理逻辑就是由这个切面完成的,这个切面的具体实现就是TransactionInterceptor类.
注意,这个TransactionInterceptor是个单例对象,所有带有@Transactional注解的方法都会经由此对象代理(省略无关代码):
- public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {
- // ......
- @Override
- @Nullable
- public Object invoke(MethodInvocation invocation) throws Throwable {
- // Work out the target class: may be {@code null}.
- // The TransactionAttributeSource should be passed the target class
- // as well as the method, which may be from an interface.
- Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
-
- // Adapt to TransactionAspectSupport's invokeWithinTransaction...
- return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
- }
- // ......
- }
-
- public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
-
- @Nullable
- protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
- final InvocationCallback invocation) throws Throwable {
-
- // ......
- // 这里TransactionAttributeSource对象保存了应用内所有方法上的@Transactional注解属性信息,利用Map来保存,其中
- // key由目标方法method对象+目标类targetClass对象组成,所以通过method+targetClass就能唯一找到目标方法上的@Transactional注解属性信息
- TransactionAttributeSource tas = getTransactionAttributeSource();
- // 这个TransactionAttribute对象就保存了目标方法@Transactional注解所有的属性配置,如timeout,propagation,readOnly等等,
- // 后续就利用这些属性完成对应的操作
- final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
- // ......
- TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
-
- Object retVal;
- try {
- // 这里最终会调用到目标方法
- retVal = invocation.proceedWithInvocation();
- } catch (Throwable ex) {
- // 目标方法抛出了异常,根据@Transactional注解属性配置决定是否需要回滚事务
- completeTransactionAfterThrowing(txInfo, ex);
- throw ex;
- }
- // ......
- // 目标方法正常执行完成,提交事务
- commitTransactionAfterReturning(txInfo);
- return retVal;
- }
- }
上面的completeTransactionAfterThrowing和commitTransactionAfterReturning方法最终也是使用的jdbc的Savepoint和Connection来完成回滚和提交,就如同我们使用原生的jdbc代码那样操作,具体的实现细节我就不展开了.关于原理解析就到这里.
@Transactional注解属性含义:
重点关注propagation,isolation,面试常常问到.
propagation各属性值含义:
关于Spring如何处理该属性参见AbstractPlatformTransactionManager类的getTransaction方法.
isolation各属性值含义:
源码github地址和gitee地址(代码同步):
https://github.com/jufeng98/java-master?github.com/jufeng98/java-master
https://gitee.com/jufeng9878/java-master?gitee.com/jufeng9878/java-master
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。