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>

示例代码见:https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day5/transaction/transfer/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>

示例代码见:https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day5/transaction/transfer/interceptor

基于tx标签的事务

相关标签:

  • <tx:advice>:配置事务;
    • id:配置id;
      • transaction-manager:指定事务管理器;
    • <tx:attributes>:配置事务属性;
      • <tx:method>:对具体方法配置事务属性,Spring事务属性见下一小节;

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>

示例代码见:https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day5/transaction/transfer/tx

基于注解的事务

相关注解:

  • @Transactional:使用在类或者方法上,增加事务功能;
  • @EnableTransactionManagement:开启事务管理;

示例代码见:https://github.com/Jun67/spring-demo/tree/master/src/main/java/com/bailiban/day5/transaction/transfer/annotation

事务练习

实现图书店收银功能。
实现以下接口:

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);
}

表:

  1. 书籍表;
    • isbn, price, stock(库存)
  2. 用户表;
    • 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。

事务属性:

  1. read-only 只读事务,便于优化。
    事务中有修改,会抛异常:TransientDataAccessResourceException
  2. timeout 设置事务超时时间;
    在指定超时时间没有获取到锁,会抛异常:QueryTimeoutException;
  3. 隔离级别;
  4. 传播规则
    可能一个操作,涉及到多个事务,某个事务里面调用了其他事务。
  • 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
当外部事务回滚时 内部会回滚 内部不会回滚
当内部事务异常
且外部事务不捕获异常时
内部会回滚,外部也会回滚
第一本书购买失败
内部会回滚,外部也会回滚
第一本书购买成功
posted @ 2019-12-29 23:07  cheng_18  阅读(223)  评论(0编辑  收藏  举报