Spring 事务
事务基础知识
Spring 事务核心对象
J2EE 开发使用分层设计的思想:
- 对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别。
- 当业务中包含多个数据层的调用时,需要在业务层开启事务,即对数据层中多个操作进行组合并归属于同一个事务进行处理。
Spring 为业务层提供了整套的事务解决方案:
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
1)PlatformTransactionManager
平台事务管理器实现类:
- DataSourceTransactionManager:适用于 Spring JDBC 或 MyBatis
- HibernateTransactionManager:适用于 Hibernate3.0 及以上版本
- JpaTransactionManager:适用于 JPA
- JdoTransactionManager:适用于 JDO
- JtaTransactionManager:适用于 JTA
标准介绍:
-
JPA(Java Persistence API):Java EE 标准之一,为 POJO 提供持久化标准规范,并规范了持久化开发的统一 API,符合 JPA 规范的开发可以在不同的 JPA 框架下运行。
-
JDO(Java Data Object):是 Java 对象持久化规范,用于存取某种数据库中的对象,并提供标准化 API。与 JDBC 相比,JDBC 仅针对关系数据库进行操作,而 JDO 可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强。
-
JTA(Java Transaction API):Java EE 标准之一,允许应用程序执行分布式事务处理。与 JDBC 相比,JDBC 事务则被限定在一个单一的数据库连接,而一个 JTA 事务可以有多个参与者,比如 JDBC 连接、JDO 等都可以参与到一个 JTA 事务中。
PlatformTransactionManager 接口定义了事务的基本操作:
// 获取事务
TransactionStatus getTransaction(TransactionDefinition definition)
// 提交事务
void commit(TransactionStatus status)
// 回滚事务
void rollback(TransactionStatus status)
2)TransactionDefinition
此接口定义了事务的基本信息:
// 获取事务定义名称
String getName()
// 获取事务的读写属性
boolean isReadOnly()
// 获取事务隔离级别
int getIsolationLevel()
// 获事务超时时间
int getTimeout()
// 获取事务传播行为特征
int getPropagationBehavior()
3)TransactionStatus
此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作:
事务控制方式
案例说明
银行转账业务说明:
-
银行转账操作中,涉及从 A 账户到 B 账户的资金转移操作。
-
本案例的数据层仅提供单条数据的基础操作,未涉及多账户间的业务操作。
案例环境:本案例环境基于 Spring、Mybatis 整合。
- 业务层接口提供转账操作:
/**
* 转账操作
* @param outName 出账用户名
* @param inName 入账用户名
* @param money 转账金额
*/
public void transfer(String outName, String inName, Double money);
- 业务层实现提供转账操作:
public void transfer(String outName, String inName, Double money){
accountDao.inMoney(outName, money); accountDao.outMoney(inName, money);
}
- 数据层提供对应的入账与出账操作:
<update id="inMoney">
update account set money = money + #{money} where name = #{name}
</update>
<update id="outMoney">
update account set money = money - #{money} where name = #{name}
</update>
1)编程式事务
public void transfer(String outName,String inName,Double money){
//创建事务管理器
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
//为事务管理器设置与数据层相同的数据源
dstm.setDataSource(dataSource);
//创建事务定义对象
TransactionDefinition td = new DefaultTransactionDefinition();
//创建事务状态对象,用于控制事务执行
TransactionStatus ts = dstm.getTransaction(td);
accountDao.inMoney(outName,money);
int i = 1/0; //模拟业务层事务过程中出现错误
accountDao.outMoney(inName,money);
//提交事务
dstm.commit(ts);
}
使用 AOP 控制事务
将业务层的事务处理功能抽取出来制作成 AOP 通知,利用环绕通知运行期动态织入:
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
配置 AOP 通知类,并注入 dataSource:
<bean id="txAdvice" class="com.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
使用环绕通知将通知类织入到原始业务对象执行过程中:
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..transfer(..))"/>
<aop:aspect ref="txAdvice">
<aop:around method="tx" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
2)声明式事务(XML)
思考:AOP 配置事务是否具有特例性?如不同的读写操作配置不同的事务类型。
public Object tx(ProceedingJoinPoint pjp) throws Throwable {
DataSourceTransactionManager dstm = new DataSourceTransactionManager();
dstm.setDataSource(dataSource);
TransactionDefinition td = new DefaultTransactionDefinition();
TransactionStatus ts = dstm.getTransaction(td);
Object ret = pjp.proceed(pjp.getArgs());
dstm.commit(ts);
return ret;
}
<bean id="txAdvice" class="com.aop.TxAdvice">
<property name="dataSource" ref="dataSource"/>
</bean>
使用 tx 命名空间配置事务专属通知类:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
</tx:attributes>
</tx:advice>
使用 aop:advisor 在 AOP 配置中引用事务专属通知类:
<aop:config>
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
aop:advice 与 aop:advisor 区别
aop:advice:配置的通知类可以是普通 java 对象,即不实现接口也不使用继承关系。
aop:advisor:配置的通知类必须实现通知接口。
- MethodBeforeAdvice
- AfterReturningAdvice
- ThrowsAdvice
- ……
tx:advice
-
类型:标签
-
归属:beans 标签
-
作用:专用于声明式事务通知
-
格式:
<beans>
<tx:advice id="txAdvice" transaction-manager="txManager">
</tx:advice>
</beans>
-
基本属性:
-
id:用于配置 aop 时指定通知器的 id
-
transaction-manager:指定事务管理器 bean
-
tx:attributes
-
类型:标签
-
归属:tx:advice 标签
-
作用:定义通知属性
-
格式:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
</tx:attributes>
</tx:advice>
-
基本属性:
- 无
tx:method
-
类型:标签
-
归属:tx:attribute 标签
-
作用:设置具体的事务属性
-
格式:
<tx:attributes>
<tx:method name="*" read-only="false" />
<tx:method name="get*" read-only="true" />
</tx:attributes>
-
说明:
- 通常事务属性会配置多个,包含 1 个读写的全事务属性,1 个只读的查询类事务属性。
-
基本属性:
3)声明式事务(注解)
@Transactional
-
类型:方法注解、类注解、接口注解
-
位置:方法定义上方、类定义上方、接口定义上方
-
作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
-
范例:
@Transactional(
readOnly = false,
timeout = -1,
isolation = Isolation.DEFAULT,
rollbackFor = {ArithmeticException.class, IOException.class},
noRollbackFor = {},
propagation = Propagation.REQUIRES_NEW
)
方式一:配置开启注解驱动(tx:annotation-driven)
-
类型:标签
-
归属:beans 标签
-
作用:开启事务注解驱动,并指定对应的事务管理器
-
范例:
<tx:annotation-driven transaction-manager="txManager"/>
方式二:纯注解驱动
-
名称:@EnableTransactionManagement
-
类型:类注解
-
位置:Spring 注解配置类上方
-
作用:开启注解驱动,等同XML格式中的注解驱动
-
范例:
// Spring 核心配置类
@Configuration
@ComponentScan("com")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class, MyBatisConfig.class, TransactionManagerConfig.class}) // 引入事务配置类
@EnableTransactionManagement
public class SpringConfig {
}
// 事务配置类
public class TransactionManagerConfig {
@Bean
public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}