SSM-Spring(四)-事务管理、多配置文件

事务管理

  1. 使用场景
    1. 在service层,通常一个方法内部可能会有几个dao操作,这些操作要么一起成功;如果中间一个操作失败,之前操作成功的都需要回滚
    2. 要实现这种操作就需要使用事务:开启事务、提交事务、回滚事务
  2. 可以通过创建一个类(TxInterceptor),然后实现MethodInterceptor接口,然后在xml中通过AOP给service层的每个类的每个方法都插入事务管理

     public class TxInterceptor implements MethodInterceptor {
         @Override
         public Object invoke(MethodInvocation invocation) throws Throwable {
             // 开启事务
        
             Object result = null;
             try {
                 result = invocation.proceed();
        
                 // 提交事务
        
             } catch (Exception e) {
                 // 回滚事务
             }
        
             return result;
         }
     }
     //xml中AOP切入略
    
  3. 但是这样做还是比较麻烦,Spring中有自带的添加事务的方法

Spring声明式事务

  1. 使用Spring的声明式事务,可以极其容易地进行事务管理
    1. 只需要在XML中进行声明配置
    2. 一般是在Serice层进行事务管理
  2. 举例使用:(该demo是一个mybatis整合项目)
    1. SkillServiceImpl层有个test方法,该方法中使用了多个dao处理数据
    2. applicationContext.xml中整合mybaitis那块如下

       <context:property-placeholder location="db.properties"/>
       <!-- 数据源(Druid) -->
       <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
           <property name="driverClassName" value="${jdbc.driverClass}"/>
           <property name="url" value="${jdbc.url}"/>
           <property name="username" value="${jdbc.username}"/>
           <property name="password" value="${jdbc.password}"/>
       </bean>
          
       <!-- 创建SqlSessionFactory -->
       <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
           <!-- 数据源 -->
           <property name="dataSource" ref="dataSource"/>
           <!-- 这个包底下的类会自动设置别名(一般是领域模型)
               这个包下面所有的别名就是类名
               比如com.zh.domain.Skill;别名是Skill
            -->
           <property name="typeAliasesPackage" value="com.zh.domain"/>
           <!-- 映射文件的位置 -->
           <property name="mapperLocations">
               <array>
                   <value>mappers/*.xml</value>
               </array>
           </property>
       </bean>
          
       <!-- 
       扫描所有的dao
       该对象可以获取到Seqsession
       可以获取到所有的dao
        -->
       <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
           <!-- 设置SqlSessionFactoryBean的id -->
           <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
           <!-- 设置dao的包 -->
           <property name="basePackage" value="com.zh.dao"/>
       </bean>
       <!--skillDao是上面扫描后的结果,也放在容器中-->
       <bean id="skillService" class="com.zh.service.impl.SkillServiceImpl">
           <property name="dao" ref="skillDao"/>
       </bean>
      
    3. 给serice层配置事务管理方面如下

       <!-- 事务管理器 -->
       <bean id="txMgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
           <!--需要设置一个数据源,可以直接用mybatis中使用的那个-->
           <property name="dataSource" ref="dataSource"/>
       </bean>
          
       <!-- 附加代码:在方法前后添加事务管理代码 Spring写好的 -->
       <tx:advice id="txAdvice" transaction-manager="txMgr">
           <!--指定哪些方法,需要进行如何事务操作 -->
           <tx:attributes>
               <!--只针对以list开头的方法-->
               <tx:method name="list*"/>
               <!--要切入的具体方法-->
               <tx:method name="list"/>
               <tx:method name="save"/>
               <tx:method name="update"/>
               <tx:method name="test"/>
           </tx:attributes>
       </tx:advice>
          
       <!-- 切面 -->
       <aop:config>
           <!--切入点:针对所有的业务层service添加事务 -->
           <aop:pointcut id="pc" expression="within(com.zh.service..*)"/>
           <!-- 要切入的附加代码 -->
           <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
       </aop:config>
      

tx:method的常用属性

  1. tx:method标签可以配置很多常用属性
  2. name的值也可以配置如下:

     *: 代表所有方法
     list* 代表list开头的方法
    

propagation

  1. 可以通过propagation属性设置事务的传播行为,用于指定当事务嵌套时,如何管理事务
  2. 当Service调用Service时,会出现事务嵌套,比如SkillServiceImpl层的test方法内部还调用了它自己的update方法,事务嵌套如下
     //service调用test方法时会开启事务,内部调用update时也会开启事务,然后处理完成提交事务,提交test开启的事务
     public void test(Skill skill) throws Exception {
         // 当前的事务情况
         update(null);
     }
    
  3. 事务嵌套的问题

     txt1 ---begin
         txt2---begin
         tet2---end
            
         ----出现异常---
            
         xt3---begin
         tet3---end
     txt1 ---end
    
    1. 上面的问题,一旦出现异常,txt2事务已经提交了,不会进行回滚
    2. 那么如何让整个txt1为原子性的呢?一旦内部某个出现异常,让之前已经提交的独立事务进行回滚呢? 可以使用propagation属性
  4. propagation常用取值

     取值                  当前有事务               当前无事务       常见使用场景
     REQUIRED(默认)        支持当前事务             开启新事务        增、删、改
     SUPPOTRS             支持当前事务              不开启新事务      查询
     MANDATORY            支持当前事务              抛出异常
     REQUIRES_NEW         挂起当前事务、开启新事务    开启新事务        日志记录
     NOT_SUPPORTED        挂起当前事务              不开启新事务
     NEVER                抛出异常                 不开启新事务
    
  5. 举例:
    1. <tx:method name="update*" propagation="REQUIRED"/>
      1. 表示test方法内部在执行update方法时,update方法会首先检查test是否已经开启事务
      2. 如果已经开启(当前有事务),那么就不在开启新事务,直接使用(支持当前事务 )
      3. 如果没有(当前无事务 ),则开启新的事务(开启新事务 )
      4. 这样test的外层事务就是一个原子,一旦内部任何一个故障,全部回滚,解决嵌套问题
      5. 可以用在增、删、改
    2. <tx:method name="update*" propagation="SUPPOTRS"/>
      1. test开启事务,则直接使用
      2. 反之,不会开启新事务
      3. 可以使用查
    3. REQUIRES_NEW
      1. test开启事务,执行update时,暂停test事务,并开启自己的事务
      2. test没有开启事务,则开启新事务
      3. 通常使用于日志记录
  6. 建议配置

     <!--
     1. 先让所有方法使用默认值,REQUIRED
     2. 再指定某些方法使用:SUPPOTRS
     -->
     <tx:method name="*" />
     <tx:method name="list*" propagation="SUPPORTS"/>
     <tx:method name="get*" propagation="SUPPORTS"/>
    

read-only

  1. 如果一个事务只执行读操作,那么数据库可能会采取某些优化措施
    1. read-only设置为true:告诉数据库这是个只读事务
    2. 只适用于REQUIRED或者REQUIRES_NEW(必须保证有事务)
  2. 举例

     <tx:method name="list*" propagation="REQUIRED" read-only="true"/>
    

timeout

  1. 单位是秒,默认是-1(按照数据库默认情况处理),超时就会抛出异常
  2. 给某个方法配置一个超时时长

     <tx:method name="list*" timeout="4"/>
    

rollback-for、no-rollback-for

  1. 默认情况下,RuntimeException、Error会导致事务回滚(非检查型异常),而Exception(检查型异常)不会
    1. rollback-for:设置哪些异常会导致事务回滚(在RuntimeException、Error的基础上增加一些异常)
    2. no-rollback-for:设置哪些异常不会导致事务回滚(在Exception的基础上增加一些异常)
  2. 举例

     <!-- 指定所有方法遇见Exception异常也要回滚-->
     <tx:method name="*" rollback-for="Exception"/>
     <tx:method name="save*" rollback-for="java.io.IOException,InterruptedException" no-rollback-for="RuntimeException,Error"/>
    

isolation(面试题)

  1. isolation用于设置事务的隔离级别
  2. 可以设置的值有

     DEFAULT(默认值,按照数据库的默认设置,一般用这个就可以)
     READ_UNCOMMITTED
     READ_COMMITTED
     REPEATABLE_READ
     SERIALIZABLE
    
  3. 事务的隔离级别:如果多个事务同时操作同一份数据,可能引发以下问题
    1. 脏读:一个事务读取到了另一个事务没有提交的数据
      1. 多个事务访问同一张表,某个事务还没有提交数据
    2. 不可重复读:一个事务范围内两个相同的查询却返回了不同数据
      1. 同一个事务2个查询中间有其他事务提交修改了数据,导致第一次、第二次查询结果不一样
    3. 幻读:一个事务发现了之前本来确认不存在的数据
      1. 第一个事务第一次查询有n条数据,再次查询发现有n+x条数据
      2. 第一次、二次中间有其他事务出入数据并提交
  4. 可以设置隔离级别来解决上述问题(由上到下,性能依次降低)

     READ UNCOMMITTED 什么也解决不了
     READ COMMITTED:可以防止脏读
     REPEATABLE READ:可以防止脏读、不可重复读( 的默认隔离级别)
     SERIALIZABLE:可以防止脏读、不可重复读、幻读
    
  5. 查询当前数据库隔离级别的SQL语句:SELECT @@TX_ISOLATION
  6. 设置数据库的隔离级别:SET GLOBAL TRANSACTION ISOLATION LEVEL 隔离级别

多个配置文件

  1. 如果Spring配置文件的内容过多,可以采取多个配置文件的形式,将各个模块单独放一个配置文件:service、mybatis、事务等
  2. 举例
    1. 有3个配置文件:

       //1. applicationContext.xml
       <bean id="person" class="com.zh.domain.Person"/>
       //2. applicationContext-mybatis.xml
       <bean id="dog" class="com.zh.domain.Dog"/>
       //3. applicationContext-tx.xml
       <bean id="user" class="com.zh.domain.User"/>
      
    2. 方法一:在人口配置文件applicationContext.xml分别倒入其他配置文件,外部使用不变

       <!--方法一:-->
       <import resource="applicationContext-mybatis.xml"/>
       <import resource="applicationContext-tx.xml"/>
          
       <bean id="person" class="com.zh.domain.Person"/>
      
    3. 方法二:入口配置文件不变,在外部使用时同时加载多个配置文件

       //方式1:一次性传入多个配置文件
       ApplicationContext ctx = new ClassPathXmlApplicationContext(
               "applicationContext.xml",
               "applicationContext-mybatis.xml",
               "applicationContext-tx.xml");
       //方式2:以applicationContext开头的所有文件都加载
       ApplicationContext ctx = new ClassPathXmlApplicationContext(
               "classpath*:applicationContext*.xml");
       System.out.println(ctx.getBean("person"));
       System.out.println(ctx.getBean("dog"));
       System.out.println(ctx.getBean("user"));
      
Table of Contents