Spring - @Transaction 事务


前言

研究 Spring 事务。

配置

package xianzhan.spring.jdbc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

/**
 * @author xianzhan
 * @since 2022-01-22
 */
@Configuration
@EnableTransactionManagement
public class JdbcConfig implements TransactionManagementConfigurer {

    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/lxz");
        dataSource.setUsername("root");
        dataSource.setPassword("root");

        return dataSource;
    }

    @Bean
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DriverManagerDataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

上面配置相当于在 XML 开启了事务注解,即:

<!-- 声明式事务管理 配置事物的注解方式注入 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

XML 配置

那么如果使用 XML 配置如何写呢?

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

    <!-- don't forget the DataSource -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    </bean>

    <!-- similarly, don't forget the TransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-declarative-first-example

代码

接下来看下业务代码:

@Service
public class StudentScoreServiceImpl implements IStudentScoreService {

    /**
     * 必须和配置名相同 txManager
     */
    @Autowired
    private PlatformTransactionManager txManager;
    @Autowired
    private JdbcTemplate               jdbcTemplate;

    @Override
    public void saveScore() {
        TransactionDefinition td = new DefaultTransactionAttribute();
        TransactionStatus transaction = txManager.getTransaction(td);

        try {
            long l = System.currentTimeMillis();
            String sql = "INSERT INTO student_score(name, subject, score) VALUE(?, ?, ?)";
            jdbcTemplate.update(sql, Long.toString(l), Long.toString(l), 1);
            jdbcTemplate.update(sql, Long.toString(l + 1), Long.toString(l + 1), 1);

            txManager.commit(transaction);
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback(transaction);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveScoreWithTransaction() {
        long l = System.currentTimeMillis();
        String sql = "INSERT INTO student_score(name, subject, score) VALUE(?, ?, ?)";
        jdbcTemplate.update(sql, Long.toString(l), Long.toString(l), 1);
        jdbcTemplate.update(sql, Long.toString(l + 1), Long.toString(l + 1), 1);
    }
}

第一个方法 saveScore 是手动控制事务放在一个 try 代码块里,发生异常则进行回滚,如果其他方法也有多个更新操作,那么也需要写相同的代码;第二个方法 saveScoreWithTransaction 则使用 @Transactional 进行拦截处理,只需写相关的业务代码即可。

可以看出,Spring 的事务注解可以帮我们解决掉很多重复的代码。

@Transactional

调用模型

调用模型

  • Caller: 调用类调用业务接口 IXxxService
  • AOP Proxy: TransactionProxyFactoryBean 生成代理类
  • Transaction Advisor: TransactionInterceptor 执行业务方法前拦截执行事务
  • Custom Advisor(s): 自定义织入(一般没用)
  • Target Method: IXxxService 实现类的业务方法,不过是由代理类执行

为什么默认需要 public 修饰的方法才能触发 @Transaction 呢?

public abstract class AbstractFallbackTransactionAttributeSource
		implements TransactionAttributeSource, EmbeddedValueResolverAware {
	// ...
	/**
	 * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result.
	 * {@link #getTransactionAttribute} is effectively a caching decorator for this method.
	 * <p>As of 4.1.8, this method can be overridden.
	 * @since 4.1.8
	 * @see #getTransactionAttribute
	 */
	@Nullable
	protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
		// Don't allow non-public methods, as configured.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
			return null;
		}

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		// First try is the method in the target class.
		TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
		if (txAttr != null) {
			return txAttr;
		}

		// Second try is the transaction attribute on the target class.
		txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
		if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
			return txAttr;
		}

		if (specificMethod != method) {
			// Fallback is to look at the original method.
			txAttr = findTransactionAttribute(method);
			if (txAttr != null) {
				return txAttr;
			}
			// Last fallback is the class of the original method.
			txAttr = findTransactionAttribute(method.getDeclaringClass());
			if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
				return txAttr;
			}
		}

		return null;
	}
	// ...
}

原因就是:

Spring 默认的 AOP 是只处理 public 方法。

为什么实现类内部方法调用 @Transaction 没有效果?

proxy

原因就是:

实现 @Transaction 的类是实现类的代理类,如果是实现类内部方法调用 @Transaction 的方法,无法执行代理类相关的事务逻辑,则 @Transaction 没有效果。

资源

spring-jdbc 代码
Spring AOP APIs
Transaction Management

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值