前文:Spring_基于Spring的JDBC
事务应该在哪一层?
前文中,我们知道Spring的JDBC会帮我们管理事务。
在这种情况下,会出现什么问题呢?
一些伪代码:
public class AccountDaoImpl implements IAccountDao{ /* 转入 */ public void transin(Long inId, BigDecimal value){ // do work } /* 转出 */ public void transout(Long outId, BigDecimal value){ // do work } }
public class AccountServiceImpl implements IAccountService{ /* 转账方法 */ public void transfer(Long inId, Long outId, BigDecimal value){ accountDao.transout(outId, value); // 转出 accountDao.transin(inId, value); // 转入 } }
可以看到,在Service层中,转账方法是通过调用DAO层的转出、转入方法实现的。
但是,由于事务是在DAO层中进行管理的,若Service层在转出后,系统出现异常,转入代码将无法继续正常执行。
正确的JDBC转账流程
1、进入Service层的转账方法时:获取DataSoure对象,得到Connection对象。
2、关闭JDBC的事务自动提交:Connection对象.setAutoCommit(false);
3、将Connection对象绑定到当前线程中。
4、调用DAO层的方法,其Connection对象要从当前线程中获取。
5、正常执行则提交事务;出现异常则回滚。
Spring的事务管理
· 相关接口
TransactionDefinition
封装事务的隔离级别,超时时间。是否为只读事务和事务的隔离级别,转播规则等事务属性。
可通过XML配置其具体信息。
PlatformTransactionManager
根据TeansactionDefinition提供的事务属性,创建事务。
TransactionStatus
封装了事务的具体运行状态。
如是否新开事务,是否已提交事务,可设置当前事务为rollback-only等。
· 事务管理的方式
Spring支持编程式事务管理和声明式事务管理。
编程式事务管理
如使用Hibernate,我们需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。
通过 Spring 提供的事务管理 API,我们可以在代码中灵活控制事务的执行。在底层,Spring 仍然将事务操作委托给底层的持久化框架来执行。
这种方式事务和业务代码耦合度太高
声明式事务管理
Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。
其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
这种方式侵入性小,把事务从业务代码中抽离出来到配置文件中,提供维护性。
· 事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器,将事务管理叫给Hibernate等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager。
通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
spring提供的事务管理器
在使用Spring的事务时,需告诉Spring使用哪一个管理器。
其中DataSourceTransactionManager为JDBC、MyBatis使用的事务管理器。
· PlatformTransactionManager中的方法
TransactionStatus getTransaction(TransactionDefinition definition)
根据TransactionDefinition事务定义信息,从事务环境返回一个已存在的事务,或创建一个新的事务。
并用TransactionStatus描述事务状态。
void commit(TransactionStatus status)
根据事务状态提交事务。
如事务状态标识为rollback-only,该方法执行回滚事务的操作。
void rollback(TransactionStatus status)
将事务回滚,当commit方法抛出异常时,rollback会被隐式调用
声明式事务管理
基于AOP,使用事务管理器,对业务代码进行事务增强。
· what :做什么增强?
做事务管理器的增强。
告诉Spring使用的是哪一个具体的事务管理器。(如JDBC使用的是DataSourceTransactionManager。)
并对事务管理器做相应的配置。(如DataSourceTransactionManager需注入DataSource对象。)
如下例:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name="dataSource" ref="dataSource" /> </bean>
· When:在什么时机增强?
事务应该是环绕增强。如下例:
<tx:advice transaction-manager="txManager" id="txAdvice">
<tx:attributes>
<tx:method name="transfer"/>
</tx:attributes>
</tx:advice>
<tx:advice>中配置transaction-manager属性,该管理器做环绕增强。
<tx:attributes>元素中<tx:method>的属性配置,可理解为对TransactionDefinition对象做属性配置以及一些额外的配置,以便事务管理器获取事务,做回滚相关操作。
其name属性值为方法名,表示在对该方法做事务增强,可使用通配符*。
更多具体配置可参考文末。
· Where:在哪里做增强?
通过AOP做相关配置,如下例:
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.hanaii.spring_tx.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint" />
</aop:config>
对切入点进行配置后,我们还需要通过<aop:advisor>将advice与pointcut关联起来。
这样Spring才知道从哪个地方where,什么时候when,做什么样what的切入。
<tx:method>中的属性
· 事务传播行为
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,
它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播。
PROPAGATION_REQUIRED
如果当前没有事务,就新建一个事务,
如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_SUPPORTS
支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY
使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW
新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
· CRUD通用的环绕增强
<tx:advice transaction-manager="txManager" id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
· 使用注解配置事务
1、在Spring配置文件中配置注解解析器和事务管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
2、使用@Transactional标签进行标注
该注解源代码:
public @interface Transactional {
String value() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
该注解标注在类上时,其配置对该类所有方法生效。
同时,标注在方法时,可对某一方法进行局部的配置(如查询方法配置read-only)。