文章目录
前言
研究 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>
代码
接下来看下业务代码:
@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
没有效果?
原因就是:
实现 @Transaction
的类是实现类的代理类,如果是实现类内部方法调用 @Transaction
的方法,无法执行代理类相关的事务逻辑,则 @Transaction
没有效果。