你真的懂spring配置事务管理吗?

Hello World

Posted by BY 人间喜剧 on October 24, 2020

你真的了解spring的事务管理吗?

自打八月份实习以来,就对小程序框架的事务很是好奇,之前的项目中,只是使用aop机制来配置事务,今天就对事务来一次彻底的解读吧:
**spring 事务管理 **分为编程式和声明式两种方式。编程式事务就是指通过编码的方式实现事务;声明是事务是基于AOP,将记题的业务逻辑与事务处理解耦。声明式的事物管理使业务的代码逻辑不受污染,因此在实际使用中还是声明式的事务用的比较多。

声明式事务有两种:

一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 的注解方式。

首先明确一下几点:

  1. 默认的spring配置事务 只会在回滚时,未检查异常(继承自RuntimeExecption的异常)或者error。
  2. @Transactional 注解只能应用到public方法才有效
  3. @Transactional注解可以被应用于接口定义和接口方法,类定义和类的public方法上,然而仅仅的@Transactional注解的出现是不足以开启事务行为的。他仅仅是一种元数据,能够可以被识别到@Transactional注解和上述的配置和适当具有事务性的beans所使用,其实是元素的出现开启了事务行为。
  4. 该注解不可以被继承,建议在具体的类或者方法上使用@Transactional注解,而不要是使用在类所实现的接口上。当然也可以在接口上使用注解,但是这将只有当你设置了基于接口的代理时他才生效。


Spring中关于事务的配置由三部分组成,分别是DataSource,TransactionManager和代理机制这三部分,无论是那种配置方式,一般变化的只是代理机制这部分。


DataSource、TransactionManager这两部分只是会根据数据的访问方式有所变化,比如Hibernate数据进行访问时,DataSource实际上为SessionFactory,TransactionManager的实现具体为:HibernateTransationManager。
具体如下图所示:

事务的TransactionManager事务管理器一共有五种,与DataSource的关联图如下所示:


根据以上代理机制的不同,一下总结了五种Spring事务的配置方法:

Spring事务的配置方式:

1.xml配置每个Bean都有一个代理

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="userDao"  
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">  
           <!-- 配置事务管理器 -->  
        <property name="transactionManager" ref="transactionManager" />     
        <property name="target" ref="userDaoTarget" />  
        <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事务属性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>  
        </property>  
    </bean>  
</beans>

2.xml的所有bean共享一个代理基类

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
       <!-- 此处所有transactionBase所有Bean共用 -->     <bean id="transactionBase"  
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"  
            lazy-init="true" abstract="true">  
        <!-- 配置事务管理器 -->  
        <property name="transactionManager" ref="transactionManager" />  
        <!-- 配置事务属性 -->  
        <property name="transactionAttributes">  
            <props>  
                <prop key="*">PROPAGATION_REQUIRED</prop>  
            </props>  
        </property>  
    </bean>    
   
    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
    <bean id="userDao" parent="transactionBase" >  
        <property name="target" ref="userDaoTarget" />   
    </bean>
</beans>

xml配置拦截器:

<?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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 
   
    <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 class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">  
        <property name="beanNames">  
            <list>  
                <value>*Dao</value>
            </list>  
        </property>  
        <property name="interceptorNames">  
            <list>  
                <value>transactionInterceptor</value>  
            </list>  
        </property>  
    </bean>  
  
    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

4. 使用AOP切面配置:

<?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: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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
 
    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
 
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />        
    </aop:config>      
</beans>

5.使用注解配置

<?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: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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">
 
    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />
 
    <tx:annotation-driven transaction-manager="transactionManager"/>
 
    <bean id="sessionFactory"  
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">  
        <property name="configLocation" value="classpath:hibernate.cfg.xml" />  
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean>  
 
    <!-- 定义事务管理器(声明式的事务) -->  
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    
</beans>

或者在springboot的启动类中通过注解开启事务管理:

@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />

  然后在具体的DAO类中添加注解@Transactional注解

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
 
    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }
}

二 Spring事务属性配置(注解示例)

我们在使用Spring声明事务时,有一个非常重要的概念就是事务的属性,事务的属性通常由事务的传播行为,事务的隔离级别,事务的超时值和事务的只读标志组成。我们在进行划分时,需要进行事务的定义,也就我们所说的事务的属性。
Spring在TransactionDefinition接口中定义了这些属性,以供PlatfromTransactionManager使用,PlatFromTransactionManager是spring事务管理的核心接口:
image.png

1.事务的传播性:

getPropagationBehavior()返回事务的传播行为,由是否一个活动的事物来决定一个事物的调用。在TransactionDefinition接口中定义了七个事物的传播行为。
配置方式:

@Transactional(propagation=Propagation.REQUIRED)    //默认方式,跟@Transactional同效果
  • PROPAGATION.REQUIRED:表示业务的方法需要在一个事务中处理,如果业务的方法执行时已经在一个事务中,则加入该事务,否则重新开启一个事务。这也是默认的事务传播行为。
  • PROPAGATION.SUPPORTS:该属性指定,如果业务方法在一个既有的事务中进行,则加入该事务;否则,业务方法将在一个没有事务的环境下进行;但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
  • PROPAGATION.MAMDATORY:该属性指定业务方法只能在已经存在的事物中进行,业务方法不能发起自己的事务,如果业务方法没有在既有的事务中进行,容器将抛出异常。
  • PROPAGATION.REQUIRED_NEW:表明业务的方法需要在一个单独事务中进行,如果业务方法在运行时已经在一个事务中,那么这个事务将会被挂起,并重新开启一个新的事务来执行这个业务方法,方法执行完毕后,原来的事务回复进行。
  • PROPAGATION_NOT_SUPPORTS:总是非事务的执行,并且挂起所有事务。
  • PROPAGATION_NEVER:总是非事务的执行,如果存在一个事务,则抛出异常。
  • PROPAGATION_NESTED:该属性指定,如果业务方法在一个既有的事务中执行,则该业务方法将在一个嵌套的事务中进行;否则,按照TransactionDefinition.PROPAGATION_REQUIRED来对待。它使用一个单独的事务,这个事务可以有多个rollback点,内部事务的rollback对外部事务没有影响,但外部事务的rollback会导致内部事务的rollback。这个行为只对DataSourceTransactionManager有效。

    1.1:PROPAGATION_REQUIRER

    ```java //事务属性:PROPAGATION_REQUIRER @Transactional(propagation=PROPAGATION_REQUIRER) public void methodA(){ …. methodB(); …. }

@Transactional(propagation=PROPAGATION_REQUIRER) public void methodB(){ … }

在我们单独调用B时,Spring保证在B方法中所有的调用都获得一个相同的链接,在调用B时没有一个Method事务,所以获得一个相同的链接,开启了一个新的事务。<br />当我们调用A时,环境中没有事务,所以开启一个新的事物,当A中调用B时环境中已经有个事务了,所以B就会加入A的事务中,两个方法同时回滚或者提交。
<a name="dItSj"></a>
#### 1.2:**PROPAGATION.SUPPORTS**
```java
//事务属性:PROPAGATION_SUPPORTS
@Transactional(propagattion=PROPAGATION.SUPPORTS)
public void MethodA{
	...
    methodB();
    ...
}
@Transactional(propagattion=PROPAGATION.SUPPORTS)
public void methodB{
	...
}

当我们在单独调用MethodB时,B是非事务执行的,
当我们调用A时,B也加入了A的事务中,事务的执行。
1.3 PROPAGATION_MANDATORY

//事务属性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    ……
    methodB();
    …… 
} 
//事务属性 PROPAGATION_MANDATORY
@Transactional(propagation=Propagation.MANDATORY)
methodB{
    …… 
}

当我们单独调用B时,因为当前没有一个活动事务,则会抛出异常

throw newIllegalTransactionStateException("Transactionpropagation 'mandatory' but no existing transaction found");

当调用methodA时,methodB则加入到methodA的事务中,事务地执行。

1.4 PROPAGATION_REQUIRES_NEW

//事务属性:PROPAGATION_REQUIRES
@Transational(propagation=PROPAGATION_REQUIRED)
mmethodA{
	doSomeThingA();
    methodB();
    doSomeThingB();
}
//事务属性:PROPAGATION_REQUIRED_NEW 
@Trancastionalpropagation=PROPAGATION.REQUIRED_NEW
methodB{
	...
}

单独调用时MethodB时,相当于把methodB声明为Required。开启一个新的事务,事务的执行。
但是我们调用A时,类似如下的效果:

main(){
    TransactionManager tm = null; 
    try{
        //获得一个JTA事务管理器
        tm = getTransactionManager(); 
        tm.begin();//开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//挂起当前事务
        try{
            tm.begin();//重新开启第二个事务
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二个事务
        }Catch(RunTimeException ex){
            ts2.rollback();//回滚第二个事务
        }finally{  
            //释放资源
        }

        //methodB执行完后,复恢第一个事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务
    }catch(RunTimeException ex){
        ts1.rollback();//回滚第一个事务
    }finally{
        //释放资源 
    }
}

在这里,我们把S1称之为外层处理器,ts2称之为内层事务。从上边的代码中我们可以看到,ts1与ts2
是两个独立的事务,两者互不相干,ts2的事务成功并不依赖于ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了methodB之外的其它代码导致的结果却被回滚了。 
  使用PROPAGATION_REQUIRES_NEW,需要使用JtaTransactionManager作为事务管理器。 
但是:

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    method1();

    User user = new User("A");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1() {
    User user = new User("B");
    userMapper.insertSelective(user);
}

我们运行这段代码发现,数据库中并没有插入数据:

从内容日志中可以看出,两个方法都处于相同的事务中,method1方法并没有创建一个新的事物。
官方文档这样解释:

In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
大概意思是:在默认的代理模式下,只有目标方法由外部调用,才能被Spring的事物拦截器拦截。在同一个类中的两个方法直接调用,是不会被spring的拦截器拦截的,就像上边的save方法直接调用了同一个类中的Method1方法,method1方法不会被Spring的事物拦截器拦截。可以使用AspectJ取代Aop代理来解决这个问题。这里我们使用最笨的方法来解决

//为了解决这个问题,我们可以新建一个类:
@Service
public class OtherServiceImpl implements OtherService {

    @Autowired
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void method1() {
        User user = new User("XXXXX");
        userMapper.insertSelective(user);
    }
}

然后在 save 方法中调用 otherService.method1 方法。

@Autowired
private OtherService otherService;

@Transactional(propagation = Propagation.REQUIRED)
@Override
public void save() {

    otherService.method1();
    User user = new User("YYYYY");
    userMapper.insertSelective(user);

    if (true) {
        throw new RuntimeException("save 抛异常了");
    }
}


从日志可以看出,首先创建了 save 方法的事务,由于 otherService.method1 方法的 @Transactional 的 propagation 属性为 Propagation.REQUIRES_NEW ,所以接着暂停了 save 方法的事务,重新创建了 otherService.method1 方法的事务,接着 otherService.method1 方法的事务提交,接着 save 方法的事务回滚。这就印证了只有目标方法由外部调用,才能被 Spring 的事务拦截器拦截。

1.5:PROPAGATION_NOT_SUPPORTED

@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事务属性 PROPAGATION_NOT_SUPPORTED
Transactional(propagation=Propagation.NOT_SUPPORTED)
methodB{
    …… 
}

当单独调用B时,不启用任何事物。而且非事务的执行。
调用A时,相当于下面的操作:

main(){
    TransactionManager tm = null;
    try{
        //获得一个JTA事务管理器
        tm = getTransactionManager();
        tm.begin();//开启一个新的事务
        Transaction ts1 = tm.getTransaction();
        doSomeThingA();
        tm.suspend();//挂起当前事务
        methodB();
        //methodB执行完后,复恢第一个事务
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一个事务
    }catch(RunTimeException ex){
        ts1.rollback();//回滚第一个事务
    }finally{
        //释放资源
    }
}

使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

1.6 PROPAGATION_NEVER

//事务属性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事务属性 PROPAGATION_NEVER
Transactional(propagation=Propagation.NEVER)
methodB{
    …… 
}

  单独调用methodB,则非事务的执行。 
  调用methodA则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘never’ butexisting transaction found”); 

1.7PROPAGATION_NESTED

这是一个嵌套的事务,使用jdbc3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理,需要驱动JDBC驱动java.sql.Savepoint类,有一些JTA的事务管理器实现实现也可能提供了相同的功能。
使用PROPAGATION_NESTED,还需要把PlatfromtransactionManager的nestedTransactionAllowed属性设为true; 而nestedTransactionAllowed属性值默认为false;

//事务属性 PROPAGATION_REQUIRED
@Transactional(propagation=Propagation.REQUIRED)
methodA{ 
    doSomeThingA(); 
    methodB();
    doSomeThingB(); 
} 
//事务属性 PROPAGATION_NESTED
Transactional(propagation=Propagation.NESTED)
methodB{
    …… 
}

如果单独调用methodB方法,则按照REQUIRED属性执行。
如果调用methodA方法,相当于:

main(){
    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con2.setSavepoint();
        try{
            methodB();
        }catch(RuntimeException ex){
            con.rollback(savepoint);
        }finally{
            //释放资源
        }
        doSomeThingB();
        con.commit();
    }catch(RuntimeException ex){
        con.rollback();
    }finally{
        //释放资源
    }
}

当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。

嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚

1.8 PROPAGATION_NESTED与PROPAGATION_REQUIRES_NEW的区别:

  • 相同点:都是一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务,但是我们使用PROPAGETION_REQUIRED_NEW时,内层事务跟外城事务一样,一但内层事务进行了提交以后,外层事务不能对其进行回滚,两个事务互不影响,两个事务不是一个真正的嵌套事务,他同时需要JTA事务管理器的支持。
  • 不同点:使用PROOAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚,而内层事务的异常不会导致外层事务的回滚。他是一个真正的嵌套事务。

    注意:

  1. Transactional的异常控制,默认是Check Exception 不回滚,unCheck Exception回滚;
  2. 如果配置了rollbackFor 和 noRollbackFor 且两个都是用同样的异常,那么遇到该异常,还是回滚
  3. rollbackFor 和noRollbackFor 配置也许不会含盖所有异常,对于遗漏的按照Check Exception 不回滚,unCheck Exception回滚;
  4. @Transactional只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.

    2.事务的隔离级别:

    在使用@Transactional的isolation属性可以指定事务的隔离级别。但是事务的隔离级别是有数据库实现的,并不是Spring实现的。

  • READ_UNCOMMITTED:会出现脏读、不可重复读和幻读问题;
  • READ_COMMITTED:会出现不可重复读和幻读问题;
  • REPEATABLE_READ:会出现幻读问题;
  • SERIALIZABLE:串行化,不会出现上面的问题。

一般数据库的隔离级别是READ_COMMITTED;Mysql默认的是REPEATABLE_READ。在众多的Mysql的引擎中,只有Innerdb是支持的事务。,所以说的事务隔离级别是Innodb下的事务分离级别。隔离级别引发的读问题如下:

3.事务的超时时间


@Transactional (propagation = Propagation.REQUIRED,timeout=30)

Spring事务超时 = 事务开始时到最后一个Statement创建时时间 + 最后一个Statement的执行时超时时间(即其queryTimeout)。
  设置了超时时间,如DataSourceTransactionManager首先开启事物会调用其doBegin方法:

int timeout = determineTimeout(definition);  
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {  
    txObject.getConnectionHolder().setTimeoutInSeconds(timeout);  
}  

其中determineTimeout用来获取我们设置的事务超时时间;然后设置到ConnectionHolder对象上(其是ResourceHolder子类),接着看ResourceHolderSupport的setTimeoutInSeconds实现:

public void setTimeoutInSeconds(int seconds) {  
    setTimeoutInMillis(seconds * 1000);  
}  
  
public void setTimeoutInMillis(long millis) {  
    this.deadline = new Date(System.currentTimeMillis() + millis);  
}

 可以看到,其会设置一个deadline时间,用来判断事务超时时间的;在JdbcTemplate中,执行sql之前,会调用其applyStatementSettings:其会调用DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());设置超时时间;

public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException {  
    Assert.notNull(stmt, "No Statement specified");  
    Assert.notNull(dataSource, "No DataSource specified");  
    ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);  
    if (holder != null && holder.hasTimeout()) {  
        // Remaining transaction timeout overrides specified value.  
        stmt.setQueryTimeout(holder.getTimeToLiveInSeconds());  
    }  
    else if (timeout > 0) {  
        // No current transaction timeout -> apply specified value.  
        stmt.setQueryTimeout(timeout);  
    }  
}

 在调用getTimeToLiveInSeconds和getTimeToLiveInMillis,会检查是否超时,如果超时设置事务回滚,并抛出TransactionTimedOutException异常。

public int getTimeToLiveInSeconds() {  
    double diff = ((double) getTimeToLiveInMillis()) / 1000;  
    int secs = (int) Math.ceil(diff);  
    checkTransactionTimeout(secs <= 0);  
    return secs;  
}  
  
public long getTimeToLiveInMillis() throws TransactionTimedOutException{  
    if (this.deadline == null) {  
        throw new IllegalStateException("No timeout specified for this resource holder");  
    }  
    long timeToLive = this.deadline.getTime() - System.currentTimeMillis();  
    checkTransactionTimeout(timeToLive <= 0);  
    return timeToLive;  
}  
private void checkTransactionTimeout(boolean deadlineReached) throws TransactionTimedOutException {  
    if (deadlineReached) {  
        setRollbackOnly();  
        throw new TransactionTimedOutException("Transaction timed out: deadline was " + this.deadline);  
    }  
}

因此,在请求之后sleep并不会超时。

public void testTimeout() throws InterruptedException {
    //放在此处会发生timeout
    Thread.sleep(3000L); 
    System.out.println(System.currentTimeMillis());  
    JdbcTemplate jdbcTemplate = new JdbcTemplate(ds);  
    jdbcTemplate.execute(" update test set hobby = hobby || '1'");  
    System.out.println(System.currentTimeMillis());  
    //放在此处并不会发生timeout
    Thread.sleep(3000L);  
}

事务的只读readonly:

readyOnly=true只读,不能进行更新删除操作。

@Transactional (propagation = Propagation.REQUIRED,readOnly=true)  

注意:我们一次执行多次查询来统计某些信息的时候,这时候,为了保证数据整体的一致性,要用只读事务。
由于事务不存在数据的修改,因此数据库要为只读事务提供一些优化手段,就例如oracle数据库对于只读事务,不启用回滚段,不记录回滚log。

  1. 在只读事务中,指定只读事务的方法为connection.setReadOnly(true);
  2. 在Hibernate中,指定只读事务的办法为: session.setFlushMode(FlushMode.NEVER); 

此时Hiberbate也会为只读事务提供session方面的一些优化手段。

  1. 在Spring的Hibernate封装中,指定只读事务的办法为: bean配置文件中,prop属性增加“readOnly”,或者用注解方式@Transactional(readOnly=true)

    @Transaction事务实现机制

    在应用系统中调用晟民哥@Transactional目标方法,Spring FrameWork默认使用AOP代理,在代码运行时,生成一个代理对象,根据 @Transactional 的属性配置信息,这个代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。
    **
    正如上文提到的,事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager 来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager 的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。