本章内容:
- Spring中事务使用
- 事务的底层操作
事务控制
环境准备(Struts框架)
用户访问—》Action --》 Service—》Dao
一个业务的成功: 调用的service是执行成功的,意味着service中调用的所有的dao是执行成功的。 事务应该在Service层统一控制。
编程式事务控制
自己手动控制事务,就叫做编程式事务控制。
Jdbc代码:
Conn.setAutoCommite(false); // 设置手动控制事务
Hibernate代码:
Session.beginTransaction(); // 开启一个事务
【细粒度的事务控制: 可以对指定的方法、指定的方法的某几行添加事务控制】
(比较灵活,但开发起来比较繁琐: 每次都要开启、提交、回滚.)
声明式事务控制
Spring提供了对事务的管理, 这个就叫声明式事务管理。
Spring提供了对事务控制的实现。用户如果想用Spring的声明式事务管理,只需要在配置文件中配置即可; 不想使用时直接移除配置。这个实现了对事务控制的最大程度的解耦。
Spring声明式事务管理,核心实现就是基于Aop。
【粗粒度的事务控制: 只能给整个方法应用事务,不可以对方法的某几行应用事务。】
(因为aop拦截的是方法。)
Spring声明式事务管理器类:
Jdbc技术:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
声明式事务管理
步骤:
1) 引入spring-aop相关的4个jar文件
2) 引入aop名称空间 【XML配置方式需要引入】
3) 引入tx名称空间 【事务方式必须引入】
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///hib_demo"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 3. dao实例 -->
<bean id="deptDao" class="cn.itcast.a_tx.DeptDao">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!-- 4. service实例 -->
<bean id="deptService" class="cn.itcast.a_tx.DeptService">
<property name="deptDao" ref="deptDao"></property>
</bean>
<!-- #############5. Spring声明式事务管理配置############### -->
<!-- 5.1 配置事务管理器类 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 5.2 配置事务增强(如果管理事务?) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 5.3 Aop配置: 拦截哪些方法(切入点表表达式) + 应用上面的事务增强配置 -->
<aop:config>
<aop:pointcut expression="execution(* cn.itcast.a_tx.DeptService.*())" id="pt"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
</aop:config>
</beans>
read-only = true;
只读事务的功能,它在整个事务中保证一致性读:在整个事务中的数据在事务开始时就决定,即使有其他会话在事务周期内修改并提交数据,也不会影响事务。可以认为在只读事务周期内,其他事务的对数据的改变就像不存在一样。
注解方式实现
使用注解实现Spring的声明式事务管理,更加简单!
步骤:
1) 必须引入Aop相关的jar文件
2) bean.xml中指定注解方式实现声明式事务管理以及应用的事务管理器类
3)在需要添加事务控制的地方,写上: @Transactional
@Transactional注解:
1)应用事务的注解
2)定义到方法上: 当前方法应用spring的声明式事务
3)定义到类上: 当前类的所有的方法都应用Spring声明式事务管理;
4)定义到父类上: 当执行父类的方法时候应用事务。
<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///hib_demo"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务管理器类 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启注解扫描 -->
<context:component-scan base-package="cn.itcast.b_anno"></context:component-scan>
<!-- 注解方式实现事务: 指定注解方式实现事务,说明是jdbc层面的事务 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
事务属性
@Transactional(
readOnly = false, // 读写事务
timeout = -1, // 事务的超时时间不限制
noRollbackFor = ArithmeticException.class, // 遇到数学异常不回滚
isolation = Isolation.DEFAULT, // 事务的隔离级别,数据库的默认
propagation = Propagation.REQUIRED // 事务的传播行为
)
隔离级别:
事务传播行为:
Propagation.REQUIRED
指定当前的方法必须在事务的环境下执行;
如果当前运行的方法,已经存在事务, 就会加入当前的事务;
Propagation.REQUIRED_NEW
指定当前的方法必须在事务的环境下执行;
如果当前运行的方法,已经存在事务: 事务会挂起; 会始终开启一个新的事务,执行完后; 刚才挂起的事务才继续运行。
即两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果, 因为它们根本就是两个事务。
举例:
Class Log{
Propagation.REQUIRED
insertLog();
}
Propagation.REQUIRED
Void saveDept(){
insertLog(); // 加入当前事务
saveDept();
.. 异常, 日志会回滚
}
Class Log{
Propagation.REQUIRED_NEW
insertLog();
}
Propagation.REQUIRED
Void saveDept(){
insertLog(); // 始终开启事务
saveDept();
.. 异常, 日志不会回滚
}
事务的源码
TransactionProxyFactoryBean类是代理bean工厂,简化事务处理。
public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBean
implements BeanFactoryAware
继承AbstractSingletonProxyFactoryBean, 该类提供了创建事务bean代理对象的方法。
public void afterPropertiesSet() {
if (this.target == null) {
throw new IllegalArgumentException("Property 'target' is required");
}
if (this.target instanceof String) {
throw new IllegalArgumentException("'target' needs to be a bean reference, not a bean name as value");
}
if (this.proxyClassLoader == null) {
this.proxyClassLoader = ClassUtils.getDefaultClassLoader();
}
ProxyFactory proxyFactory = new ProxyFactory();
if (this.preInterceptors != null) {
for (Object interceptor : this.preInterceptors) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor));
}
}
// Add the main interceptor (typically an Advisor).
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(createMainInterceptor()));
if (this.postInterceptors != null) {
for (Object interceptor : this.postInterceptors) {
proxyFactory.addAdvisor(this.advisorAdapterRegistry.wrap(interceptor));
}
}
proxyFactory.copyFrom(this);
TargetSource targetSource = createTargetSource(this.target);
proxyFactory.setTargetSource(targetSource);
if (this.proxyInterfaces != null) {
proxyFactory.setInterfaces(this.proxyInterfaces);
}
else if (!isProxyTargetClass()) {
// Rely on AOP infrastructure to tell us what interfaces to proxy.
proxyFactory.setInterfaces(
ClassUtils.getAllInterfacesForClass(targetSource.getTargetClass(), this.proxyClassLoader));
}
this.proxy = proxyFactory.getProxy(this.proxyClassLoader);/**此处创建了代理对象**/
}
而在该方法中调用了createMainInterceptor方法,该方法很重要,获取到事务的拦截对象,进入到该方法中:
protected Object createMainInterceptor() {
this.transactionInterceptor.afterPropertiesSet();
if (this.pointcut != null) {
return new DefaultPointcutAdvisor(this.pointcut, this.transactionInterceptor);
}
else {
// Rely on default pointcut.
return new TransactionAttributeSourceAdvisor(this.transactionInterceptor);
}
}
这样,当我们调用业务bean的处理方法时,实际上是调用代理对象Interceptor的Invoker方法:
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor,Serializable {
public Object invoke(final MethodInvocationinvocation) throws Throwable {
.......
//就在这一句开启事务
TransactionInfotxInfo = createTransactionIfNecessary(tm, txAttr,joinpointIdentification);
......
retVal= invocation.proceed(); //执行下一个拦截器 或 执行代理目标的方法
......
commitTransactionAfterReturning(txInfo); <span style="font-family: Arial, Helvetica, sans-serif;">//提交事务 </span>
return retVal;
.......
}
}
该方法中实现了真正的在业务bean方法中加入事务处理,现在我们来分析下这段源码。
我们注意到这段代码:TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification),该代码就决定了是否启用事务处理。深入到该方法内部,
该方法调用了status = tm.getTransaction(txAttr),这里就是获取事务的关键部分。进入该方法的实现:
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
......
public final TransactionStatus getTransaction(TransactionDefinitiondefinition) throws TransactionException {
</span>
//这里是取得当前已绑定到TransactionSynchronizationManager的上下文的事务,主要为事务传播行为而设
Object transaction =doGetTransaction();
........
//如果事务已存在,根据事务传播行为来处理,
if(isExistingTransaction(transaction)) {
// Existingtransaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
......
//这里最关键,开启一个新的事务,并放进TransactionSynchronizationManager的上下文中,并绑定到当前线程)
doBegin(transaction,definition);
return status;
.......
}
......
}
AbstractPlatformTransactionManager抽象类是spring事务处理中的一个核心类,该类实现接口PlatformTransactionManager,他们之间的关系如下图示
如上图所示,spring实现了几种方式的事务处理,其中有JTA,DataSource,Hibernate,JDO,JPA,CCI。现在我们进入HibernateTransactionManager的doGenTransaction的方法内部,查看他是如何处理的,其他的几个都是类似的。
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
protected Object doGetTransaction() {
HibernateTransactionObject txObject = new HibernateTransactionObject();
......
//在TransactionSynchronizationManager的上下文中查找当前的Session(实质上也是与线程绑定的)
SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory());
......
return txObject;
}
}
该方法尝试获取当前已绑定到TransactionSynchronizationManager的上下文的事务,主要为事务传播行为而设定。然后进入doBegin()方法,该方法尝试开启一个新的事务,并放进TransactionSynchronizationManager的上下文(ThreadLocal的Map)中,绑定到当前线程中(线程的ThreadLocal变量中)。
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager, InitializingBean {
......
protected void doBegin(Object transaction, TransactionDefinition definition) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
.........
try {
if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
//这里才真正的打开Session
Session newSession = SessionFactoryUtils.openSession(getSessionFactory());
.........
}
session = txObject.getSessionHolder().getSession();
.........
Transaction hibTx;
// Register transaction timeout.
.........
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
.........
}
else {
//这里才真正地开启事务
hibTx = session.beginTransaction();
}
.........
//如果这新开启的Session,则将SessionHolder(Session和Transaction)放到TransactionSynchronizationManager的上下文中(绑定到当前线程)
// Bind the session holder to the thread.
if (txObject.isNewSessionHolder()) {
//以键值对<SessionFactory,SessionHolder>形式绑定到TransactionSynchronizationManager的上下文中
TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder());
}
.......
}
.........
}
......
}
主要是理解TransactionSynchronizationManager, 要懂得Spring的TransactionManager开启事务后是以键值对<SessionFactory,SessionHolder>形式 存放到 TransactionSynchronizationManager的上下文(ThreadLocal的Map)中(以ThreadLocal的方式与当前线程绑定)。
在获取Session,开启事务后,又回到TransactionInterceptor 的Invoker方法中,进行实际业务的处理:retVal= invocation.proceed(),处理完之后,则开始提交事务或者回滚事务。
成功后提交事务:commitTransactionAfterReturning(txInfo),进入该方法内部就可发现是调用了PlatformTransactionManager.commin方法进行提交。
失败后回滚事务:completeTransactionAfterThrowing(txInfo, ex)也是类似。
至此Spring的基于动态代理方式实现事务处理的流程就讲完了。