Day7 Spring事务管理(2)
Spring事务使用
基于bean配置的事务
事务相关bean:
DataSourceTransactionManager
:JDBC 事务管理器,提供事务管理。
TransactionProxyFactoryBean
:事务管理相关的代理工厂类。
xml配置参考:
<!-- 配置druid数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- sql操作模板类-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- JDBC 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- DAO 层-->
<bean id="accountDao" class="com.bailiban.day6.beanxml.AccountDao">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!-- Service层-->
<bean id="accountService" class="com.bailiban.day6.beanxml.AccountService">
<property name="accountDao" ref="accountDao" />
</bean>
<!-- 动态代理-->
<bean id="accountServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 指定事务管理器-->
<property name="transactionManager" ref="transactionManager" />
<!-- 指定代理对象-->
<property name="target" ref="accountService" />
<!-- 指定事务属性 -->
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
基于拦截器的事务
相关bean:
- TransactionInterceptor: 事务拦截器;
- BeanNameAutoProxyCreator:自动生成代理,在前述AOP示例代码https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day4/aop/spring12/autoproxy 中,我们使用过该类;
xml配置参考:
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="serviceProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Service" />
<property name="interceptorNames" value="transactionInterceptor" />
</bean>
基于tx标签的事务
相关标签:
<tx:advice>
:配置事务;- id:配置id;
- transaction-manager:指定事务管理器;
<tx:attributes>
:配置事务属性;<tx:method>
:对具体方法配置事务属性,Spring事务属性见下一小节;
- id:配置id;
xml配置参考:
<tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="transactionInterceptor" pointcut="execution(* com..bean.AccountService.*(..))" />
</aop:config>
基于注解的事务
相关注解:
- @Transactional:使用在类或者方法上,增加事务功能;
- @EnableTransactionManagement:开启事务管理;
事务练习
实现图书店收银功能。
实现以下接口:
public interface BookShopDao {
// 根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
// 更新书的库存,使书号对应的库存-1
public void updateBookStock(String isbn);
// 更新用户的账户余额:account的balance-price
public void updateUserAccount(String username, int price);
}
public interface BookShopService {
public void purchase(String username, String isbn);
}
public interface Cashier {
public void checkout(String username, List<String> isbns);
}
表:
- 书籍表;
- isbn, price, stock(库存)
- 用户表;
- id, username, money
测试:购买书籍的行为。
代码参考:https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day5/transaction/annotation
Spring事务属性
Spring使用PlatformTransactionManager
来管理事务,具体事务管理使用了特定的管理类,如JDBC、Hibernate等。
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
从这里可知具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。
事务属性:
- read-only 只读事务,便于优化。
事务中有修改,会抛异常:TransientDataAccessResourceException - timeout 设置事务超时时间;
在指定超时时间没有获取到锁,会抛异常:QueryTimeoutException; - 隔离级别;
- 传播规则
可能一个操作,涉及到多个事务,某个事务里面调用了其他事务。
- REQUIRED:事务默认的传播规则,如果当前没有事务,则开启一个新事务;如果当前已存在一个事务,则使用已存在的事务。
- 内部事务和外部事务公用一套事务;
- 内部事务的属性如果与外部事务冲突,会使用外部的事务属性;
- 不管是内部事务,还是外部事务,当有异常回滚时,所有操作都会回滚;
示例1:
余额:10,书籍信息(1001:10:10, 1002:15:10, isbn:价格:库存)
购书:(1001, 1002)
疑问:1001能否成功购买?
两本书都购买失败,说明内部事务异常,会导致整个事务回滚;
示例2:
余额:25,书籍信息同上。
购书:(1001, 1002)
代码修改:在checkout最后,抛一个异常。构造一个外部事务异常的场景。
疑问:purchase可以成功提交吗?
购书失败,说明外部事务异常,会导致内部成功的操作被回滚。
示例3:
余额:10,书籍信息(1001:10:10, 1002:15:10, isbn:价格:库存)
购书:(1001, 1002)
代码修改:checkout捕获了purchase的异常。
疑问:1001能否成功购买?
外部事务出现异常:
Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
结论:当内部事务异常,且外部事务捕获了该异常,此时整个事务都会回滚,因为内部事务设置了rollback-only标记,导致外部事务在提交时检测到该标记,发送回滚。
- REQUIRES_NEW:开启一个新事务,如果之前有事务存在,则之前事务挂起。
示例4:在上述示例3中,
当checkout没有捕获异常时,第一本书是否可成功购买?
可以,因为购买第一本书是一个单独的事务,且已成功提交。
当checkout捕获异常时,第一本书是否可成功购买?
示例5:
外部事务有其他操作,如bookShopDao.updateBookStock("1003");
;
余额只能买一本书
checkout捕获了异常:外部事务可以执行成功,且第一本书购买成功;
checkout没有捕获异常:外部事务回滚,第一本书购买成功;
-
SUPPORTS:当前存在事务时,会进入事务执行;当前无事务时,以非事务方式执行;
-
MANDATORY:必须在事务内执行,如果当前无事务,会抛异常;
-
NOT_SUPPORTED:以非事务方式执行,当前如有事务,则被挂起;
-
NEVER:以非事务方式执行,当前如有事务,则抛异常;
-
NESTED:以嵌套事务执行
如果当前存在事务,则以嵌套事务的方式执行;
如果当前不存在事务,则开启新事务执行。嵌套事务的方式:
- 当外部事务异常回滚时,嵌套事务也会回滚;
- 当嵌套事务异常回滚时,外部事务不会回滚。
示例6:
- 余额充足,checkout在结束时抛异常。
结果:购书失败,外面事务也会回滚。 - 余额不足
- 当外部事务不捕获异常时,由于内部事务会抛出异常,导致外部事务也异常回滚;
- 当外部事务捕获异常时,嵌套的内部事务回滚不会影响外部事务的提交。
NESTED 与 REQUIRES_NEW区别:
NESTED | REQUIRES_NEW | |
---|---|---|
当外部事务回滚时 | 内部会回滚 | 内部不会回滚 |
当内部事务异常 且外部事务不捕获异常时 |
内部会回滚,外部也会回滚 第一本书购买失败 |
内部会回滚,外部也会回滚 第一本书购买成功 |