SPRING事务实现
事务传播行为种类
Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,
它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:
事务传播行为类型 |
说明 |
PROPAGATION_REQUIRED |
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS |
支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY |
使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW |
新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED |
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER |
以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED |
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
ibatis spring 事务实现
1.配置动态数据源-根据KEY路由
<bean id="dataSource" class="com.xiao.AbstractRoutingDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="db2" value-ref="ds-db2" /> <entry key="sqlserver" value-ref="ds-sql" /> </map> </property> <property name="defaultTargetDataSource" ref="ds-db2" /> </bean>
2.配置事务
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="insert*" propagation="REQUIRED" rollback-for="Throwable" /> <tx:method name="delete*" propagation="REQUIRED" rollback-for="Throwable" /> <tx:method name="update*" propagation="REQUIRED" rollback-for="Throwable" /> <tx:method name="toggle*" propagation="REQUIRED" rollback-for="Throwable" /> <tx:method name="query*" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="servicePointcut" expression="execution(* com.xiao..*ServiceImpl.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" /> <!-- <aop:advisor advice-ref="txAdvice" pointcut-ref="otherPointcut" /> --> </aop:config>
3.配置IBATIS
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocations"> <list> <value>classpath:/com/xiao/sqlmap-config.xml</value> </list> </property> <property name="dataSource" ref="dataSource" /> <property name="lobHandler" ref="lobHandler" /> </bean>
4.注解事务配置
XML加上 <tx:annotation-driven transaction-manager="transactionManager"/>
在实现类加上这个
@Transactional(propagation=Propagation.NOT_SUPPORTED,rollbackFor=RuntimeException.class)
问题:事务方法中,多个DAO的连接用的应该是同一个connection,SPRING是如何让sqlMapClient的connection和
transactionManager中一致的呢?
分析:可以看到它们用到的是同一个数据源。在SqlMapClientFactoryBean.afterPropertiesSet中会将datasource封装成
TransactionAwareDataSourceProxy一个代理。代理了getConnection方法。
实现细节见DataSourceUtils.doGetConnection ,该方法从当前线程中取线程本地数据(ThreadLocal)MAP,key为数据源,
值为连接,这个如果是事务中,事务的AOP会在进入事务方法前放入这个连接。所以取的连接是同一个。
问题:事务中切换动态数据源切换没用?
分析:根据上面分析,取连接时取的是进入事务方法前设置的连接,所以不行。AbstractRoutingDataSource实际是getConnection
方法动态切换数据源,但这个方法被代理了。而XML配置的DAO和事务的数据源是同一个。
事务分析:
TransactionInterceptor拦截事务方法,第一次进入,当前线程对应的ConnectionHolder为空,
调用doBegin方法,生成一个新的数据库连接ConnectionHolder,事务Transact类设置为newConnectionHolder
属性为TRUE,标记这个Transact为第一次,如果是第一次的Transact,绑定ConnectionHolder,到线程本地上下文
中,如果事务方法中还是嵌套事务情况,假设传播级别是PROPAGATION_REQUIRED(用以前的事务),进入嵌套事务方法时,连接取的
还是第一次的ConnectionHolder,但Transact类设置为newTransaction false(用来区分事务是否要提交);
Propagation.NOT_SUPPORTED:会将之前的事务挂起,当前线程的连接清空;NOT_SUPPORTED方法里的后续的SQL会直接提交,如果前后操作的SQL是同一条数据,
会引起死锁,前面的事务锁住了数据,后续事务无法提交。
Propagation.REQUIRES_NEW:会将之前的事务挂起,新建一个事务,当前线程的连接设置成新事务的数据库连接;新事务提交后,恢复之前的事务,当前线程的数据库连接
设置成恢复事务的连接。保证第一个事务的SQL用的是同一个连接。