首页 > 编程语言 > 详细

spring-事务实现和@Transactional注解分析

时间:2020-11-16 19:47:42      阅读:45      评论:0      收藏:0      [点我收藏+]

事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
Spring Framework对事务管理提供了一致的抽象,其特点如下:

  • 为不同的事务API提供一致的编程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
  • 支持声明式事务管理,特别是基于注解的声明式事务管理,简单易用
  • 提供比其他事务API如JTA更简单的编程式事务管理API
  • 与spring数据访问抽象的完美集成

事务管理方式

spring支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

自动提交(AutoCommit)与连接关闭时的是否自动提交

自动提交

默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果
执行失败则隐式的回滚事务。

有些数据连接池提供了关闭事务自动提交的设置,最好在设置连接池时就将其关闭。但C3P0没有提供这一特性,只能依靠spring来设置。
因为JDBC规范规定,当连接对象建立时应该处于自动提交模式,这是跨DBMS的缺省值,如果需要,必须显式的关闭自动提交。C3P0遵守这一规范,让客户代码来显式的设置需要的提交模式。

连接关闭时的是否自动提交

当一个连接关闭时,如果有未提交的事务应该如何处理?JDBC规范没有提及,C3P0默认的策略是回滚任何未提交的事务。这是一个正确的策略,但JDBC驱动提供商之间对此问题并没有达成一致。
C3P0的autoCommitOnClose属性默认是false,没有十分必要不要动它。或者可以显式的设置此属性为false,这样会更明确。

基于注解的声明式事务管理配置
spring-servlet.xml

技术分享图片
技术分享图片
1 <!-- transaction support-->
2 <!-- PlatformTransactionMnager -->
3 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4     <property name="dataSource" ref="dataSource" />
5 </bean>
6 <!-- enable transaction annotation support -->
7 <tx:annotation-driven transaction-manager="txManager" />
技术分享图片
技术分享图片

还要在spring-servlet.xml中添加tx名字空间

技术分享图片
技术分享图片
 1 ...
 2     xmlns:tx="http://www.springframework.org/schema/tx"
 3     xmlns:aop="http://www.springframework.org/schema/aop"
 4     xsi:schemaLocation="
 5     ...
 6  
 7 http://www.springframework.org/schema/tx
 8  
 9  
10 http://www.springframework.org/schema/tx/spring-tx.xsd
11  
12     ...
技术分享图片
技术分享图片

MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。

另外需要下载依赖包aopalliance.jar放置到WEB-INF/lib目录下。否则spring初始化时会报异常
java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor

spring事务特性

TransactionDefinition接口定义以下特性:

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
  • TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
  • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务只读属性

只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务。

spring事务回滚规则

指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

@Transactional注解

@Transactional属性

 
属性类型描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

 

用法

1. 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

2. @Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

3. 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

4. 通过 元素的 "proxy-target-class" 属性值来控制是基于接口的还是基于类的代理被创建。如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。


标准的JDK基于接口的代理将起作用-->
proxy-target-class="false"/>

基于类的代理将起作用 ,同时 cglib.jar必须在CLASSPATH中
proxy-target-class="true"/>
-->


非JTA事务(即非分布式事务), 事务配置的时候 ,需要指定dataSource属性(非分布式事务,事务是在数据库创建的链接上开启。)-->


JTA事务(非分布式事务), 事务配置的时候 ,不能指定dataSource属性(分布式事务,是有全局事务来管理数据库链接的)-->

注解@Transactional cglib与java动态代理最大区别是代理目标对象不用实现接口,那么注解要是写到接口方法上,要是使用cglib代理,这是注解事物就失效了,为了保持兼容注解最好都写到实现类方法上。

5. Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

6. @Transactional 的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的。

public interface PersonService {
//删除指定id的person
public void delete(Integer personid) ;

//删除指定id的person,flag
public void delete(Integer personid,boolean flag) ;
}

public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;

public void delete(Integer personid){
try{
this.delete(personid,true)
System.out.println("delete success");
}catch(Exception e){
System.out.println("delete failed");
}
}

@Transactional
//此时,事务根本就没有开启, 即数据库会默认提交该操作,即记录别删除掉 public void delete(Integer personid,boolean flag){
if(flag == ture){
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("运行期例外");
}
}
}

public class PersonServiceBeanTest{
PersonService ps = new PersonServiceBean ();
ps.delete(5);
}

7. Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

-----------------------------------------------------------------------------------------------------------------------------------------------
public interface PersonService {
//删除指定id的person
public void delete(Integer personid) ;

//获取person
public Person getPerson(Integer personid);
}

//PersonServiceBean 实现了PersonService 接口,则基于接口的还是基于类的代理 都可以实现事务
@Transactional public class PersonServiceBean implements PersonService {
private JdbcTemplate jdbcTemplate;

//发生了unchecked异常,事务回滚, @Transactional
public void delete(Integer personid){
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("运行期例外");
}
}

---------------------------------------------------------------------------------------------------------------------------------------------------
public interface PersonService {
//删除指定id的person
public void delete(Integer personid) throws Exception;

//获取person
public Person getPerson(Integer personid);
}

@Transactional
public class PersonServiceBean implements PersonService {

//发生了checked异常,事务不回滚,即数据库记录仍能被删除,
//checked的例外,需要我们在外部用try/catch语法对调用该方法的地方进行包含 @Transactional
public void delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new Exception("运行期例外");
}

}
---------------------------------------------------------------------------------------------------------------------------------------------------
但是,对于checked这种例外,默认情况下它是不会进行事务回滚的,但是如果我们需要它进行事务回滚,这时候可以在delete方法上通过@Transaction这个注解来修改它的行为。

@Transactional
public class PersonServiceBean implements PersonService {

@Transactional(rollbackFor=Exception.class)
//rollbackFor这属性指定了,既使你出现了checked这种例外,那么它也会对事务进行回滚
public void delete(Integer personid) throws Exception{
jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
new int[]{java.sql.Types.INTEGER});
throw new Exception("运行期例外");
}
}
---------------------------------------------------------------------------------------------------------------------------------------------------

在PersonServiceBean这个业务bean里面,有一些事务是不需要事务管理的,好比说获取数据的getPersons方法,getPerson方法。因为@Transactional 放在了类的上面。


此时,可以采用propagation这个事务属性@Transactional(propagation=Propagation.NOT_SUPPORTED),propagation这个属性指定了事务传播行为,我们可以指定它不支持事务,当我们这么写了之后,Spring容器在getPersons方法执行前就不会开启事务.

@Transactional
public class PersonServiceBean implements PersonService {

@Transactional(propagation=Propagation.NOT_SUPPORTED)
//则此方法 就不会开启事务了
public Person getPerson(Integer personid)
{
}
}

 

 

 

 

 

transaction注解分析

1. Spring事务的基本原理

事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional 注解的事务管理。
使用@Transactional的相比传统的我们需要手动开启事务,然后提交事务来说。它提供如下方便

  • 根据你的配置,设置是否自动开启事务
  • 自动提交事务或者遇到异常自动回滚

声明式事务(@Transactional)基本原理如下:

  1. 配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
  2. spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
  3. 真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
2. @Transactional基本配置解析
@Transactional
    public void saveUser(){

        User user = new User();
        user.setAge(22);
        user.setName("maskwang");
        logger.info("save the user{}",user);
        userRepository.save(user);
    }

如上面这个例子一样,很轻松的就能应用事务。只需要在方法上加入@Transactional注解。当@Transactional加在方法上,表示对该方法应用事务。当加在类上,表示对该类里面所有的方法都应用相同配置的事务。接下来对@Transactional的参数解析。

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}
  • transactionManager()表示应用哪个TransactionManager.常用的有如下的事务管理器
 
技术分享图片
image.png
  • isolation()表示隔离级别
 
技术分享图片
image.png

脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。

不可重复读的重点是修改 :同样的条件, 你读取过的数据,再次读取出来发现值不一样了幻读的重点在于新增或者删除:同样的条件, 第 1 次和第 2 次读出来的记录数不一样

  • propagation()表示事务的传播属性
    事务的传播是指事务的嵌套的时候,它们的事务属性。常见的传播属性有如下几个
  1. PROPAGATION_REQUIRED(spring 默认)
    假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()。如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。不管如何,ServiceB.methodB()都会在事务中。

  2. PROPAGATION_REQUIRES_NEW
    比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。它与1中的区别在于ServiceB.methodB() 新起了一个事务。如过ServiceA.methodA() 发生异常,ServiceB.methodB() 已经提交的事务是不会回滚的。

  3. PROPAGATION_SUPPORTS
    假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。
    剩下几种就不多介绍,可以参考这篇文章。https://www.jianshu.com/p/249f2cd42692

  • readOnly() 事务超时设置.超过这个时间,发生回滚

  • readOnly() 只读事务
    从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。
    注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务

  • rollbackFor()导致事务回滚的异常类数组.

  • rollbackForClassName() 导致事务回滚的异常类名字数组

  • noRollbackFor 不会导致事务回滚的异常类数组

  • noRollbackForClassName 不会导致事务回滚的异常类名字数组

3. @Transactional 使用应该注意的地方
3.1 默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。你如果想要在特定的异常回滚可以考虑rollbackFor()等属性
3.2 @Transactional 只能应用到 public 方法才有效。

这是因为在使用 Spring AOP 代理时,Spring 会调用 TransactionInterceptor在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptorCglibAopProxy的内部类)的的 intercept方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取@Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

@Nullable
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
       //这里判断是否是public方法
        if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        } 
//省略其他代码

若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。整个事务执行的时序图如下。


 
技术分享图片
image.png
3.3 Spring 的 AOP 的自调用问题

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional注解的方法内部调用有@Transactional注解的方法,有@Transactional注解的方法的事务被忽略,不会发生回滚。这个问题是由于Spring AOP 代理造成的(如下面代码所示)。之所以没有应用事务,是因为在内部调用,而代理后的类(把目标类作为成员变量静态代理)只是调用成员变量中的对应方法,自然也就没有aop中的advice,造成只能调用父类的方法。另外一个问题是只能应用在public方法上。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。

@Transactional
 public void saveUser(){
        User user = new User();
        user.setAge(22);
        user.setName("mask");
        logger.info("save the user{}",user);
        userRepository.save(user);
       // throw new RuntimeException("exception");
    }
 public void saveUserBack(){
        saveUser();   //自调用发生
    }
3.4 自注入解决办法
@Service
public class UserService {

    Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    UserRepository userRepository;
    
    @Autowired 
    UserService userService; //自注入来解决

    @Transactional
    public void saveUser(){

        User user = new User();
        user.setAge(22);
        user.setName("mask");
        logger.info("save the user{}",user);
        userRepository.save(user);
       // throw new RuntimeException("exception");
    }
 public void saveUserBack(){
        saveUser();
    }
}

另外也可以把注解加到类上来解决。

3.4注意事项

  • @Transactional注解不支持多数据源的情况
  • 如果存在多个数据源且未指定具体的事务管理器,那么实际上启用的事务管理器是最先在配置文件中指定的(即先加载的)
  •  Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

4.spring事务回滚规则

指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

4. 总结

@Transactional用起来是方便,但是我们需要明白它背后的原理,避免入坑。另外@Transactional不建议用在处理时间过长的事务。因为,它会一直持有数据库线程池的连接,造成不能及时返回。就是尽量是的事务的处理时间短。

参考文章:

Spring @Transactional的使用及原理
深入理解 Spring 事务原理
透彻的掌握 Spring 中@transactional 的使用
在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法

https://www.cnblogs.com/yepei/p/4716112.html


作者:maskwang520
链接:https://www.jianshu.com/p/5687e2a38fbc
來源:简书

 

 

 

 

 

 

@Transactional 详解

@Transactional 是声明式事务管理 编程中使用的注解

1 .添加位置

1)接口实现类或接口实现方法上,而不是接口类中。
2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:只读的接口就不需要事务管理,由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。

3)错误使用:

  1.  
    1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
  2.  
     
  3.  
    2.接口中异常(运行时异常)被捕获而没有被抛出。
  4.  
    默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,
  5.  
    也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),
  6.  
    而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。
  7.  
     
  8.  
    3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,
  9.  
    也不能获取spring 注入的 bean 。
  10.  
    在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
  11.  
    一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,
  12.  
    不会回滚线程中调用方法的事务。
  13.  
     


2.声明式事务管理实现方式:
基于 tx 和 aop 名字空间的 xml 配置文件

  1.  
    // 基本配置
  2.  
    <?xml version="1.0" encoding="UTF-8"?>
  3.  
    <beans xmlns="http://www.springframework.org/schema/beans"
  4.  
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
  5.  
           xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
  6.  
           xmlns:task="http://www.springframework.org/schema/task" xmlns:jms="http://www.springframework.org/schema/jms"
  7.  
           xmlns:tx="http://www.springframework.org/schema/tx"
  8.  
           xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
  9.  
                              http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
  10.  
                              http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
  11.  
                              http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
  12.  
                              http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
  13.  
                              http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
  14.  
                              http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
  15.  
     
  16.  
    <bean name="transactionManager"
  17.  
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  18.  
            <property name="dataSource" ref="shardingDataSource"></property>
  19.  
        </bean>
  20.  
     
  21.  
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
  22.  
     
  23.  
    // MyBatis 自动参与到 spring 事务管理中,无需额外配置,
  24.  
    只要 org.mybatis.spring.SqlSessionFactoryBean 引用的数据源与
  25.  
    DataSourceTransactionManager 引用的数据源一致即可,否则事务管理会不起作用。
  26.  
     
  27.  
    // <annotation-driven> 标签的声明,
  28.  
    是在 Spring 内部启用 @Transactional 来进行事务管理,使用 @Transactional 前需要配置。
  29.  
     


3. @Transactional注解
@Transactional 实质是使用了 JDBC 的事务来进行事务控制的
@Transactional 基于 Spring 的动态代理的机制
 

  1.  
    @Transactional 实现原理:
  2.  
     
  3.  
    1) 事务开始时,通过AOP机制,生成一个代理connection对象,
  4.  
    并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。
  5.  
    在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,
  6.  
    执行所有数据库命令。
  7.  
    [不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]
  8.  
    (物理连接 connection 逻辑上新建一个会话session;
  9.  
    DataSource TransactionManager 配置相同的数据源)
  10.  
     
  11.  
    2) 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,
  12.  
    然后关闭该代理 connection 对象。
  13.  
    (事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)


4.声明式事务的管理实现本质:
事务的两种开启方式:
      显示开启 start transaction | begin,通过 commit | rollback 结束事务
      关闭数据库中自动提交 autocommit set autocommit = 0;MySQL 默认开启自动提交;通过手动提交或执行回滚操作来结束事务


Spring 关闭数据库中自动提交:在方法执行前关闭自动提交,方法执行完毕后再开启自动提交

  1.  
     // org.springframework.jdbc.datasource.DataSourceTransactionManager.java 源码实现
  2.  
     // switch to manual commit if necessary. this is very expensive in some jdbc drivers,
  3.  
     // so we don‘t want to do it unnecessarily (for example if we‘ve explicitly
  4.  
     // configured the connection pool to set it already).
  5.  
     if (con.getautocommit()) {
  6.  
         txobject.setmustrestoreautocommit(true);
  7.  
         if (logger.isdebugenabled()) {
  8.  
             logger.debug("switching jdbc connection [" + con + "] to manual commit");
  9.  
         }
  10.  
         con.setautocommit(false);
  11.  
     }
  12.  
     

问题:

关闭自动提交后,若事务一直未完成,即未手动执行 commit 或 rollback 时如何处理已经执行过的SQL操作?

C3P0 默认的策略是回滚任何未提交的事务
C3P0 是一个开源的JDBC连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有 Hibernate,Spring等
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互

-------------------------------------------------------------------------------------------------------------------------------
5. spring 事务特性
spring 所有的事务管理策略类都继承自 org.springframework.transaction.PlatformTransactionManager 接口

  1.  
    public interface PlatformTransactionManager {
  2.  
      TransactionStatus getTransaction(TransactionDefinition definition)
  3.  
      throws TransactionException;
  4.  
      void commit(TransactionStatus status) throws TransactionException;
  5.  
      void rollback(TransactionStatus status) throws TransactionException;
  6.  
    }

事务的隔离级别:是指若干个并发的事务之间的隔离程度

  1.  
    1. @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读,
  2.  
    不可重复读) 基本不使用
  3.  
     
  4.  
    2. @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
  5.  
     
  6.  
    3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
  7.  
     
  8.  
    4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化

事务传播行为:如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为

  1.  
    1. TransactionDefinition.PROPAGATION_REQUIRED:
  2.  
    如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
  3.  
     
  4.  
    2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
  5.  
    创建一个新的事务,如果当前存在事务,则把当前事务挂起。
  6.  
     
  7.  
    3. TransactionDefinition.PROPAGATION_SUPPORTS:
  8.  
    如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  9.  
     
  10.  
    4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
  11.  
    以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  12.  
     
  13.  
    5. TransactionDefinition.PROPAGATION_NEVER:
  14.  
    以非事务方式运行,如果当前存在事务,则抛出异常。
  15.  
     
  16.  
    6. TransactionDefinition.PROPAGATION_MANDATORY:
  17.  
    如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  18.  
     
  19.  
    7. TransactionDefinition.PROPAGATION_NESTED:
  20.  
    如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
  21.  
    如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

技术分享图片

上表字段说明:

  1.  
    1. value :主要用来指定不同的事务管理器;
  2.  
    主要用来满足在同一个系统中,存在不同的事务管理器。
  3.  
    比如在Spring中,声明了两种事务管理器txManager1, txManager2.然后,
  4.  
    用户可以根据这个参数来根据需要指定特定的txManager.
  5.  
     
  6.  
    2. value 适用场景:在一个系统中,需要访问多个数据源或者多个数据库,
  7.  
    则必然会配置多个事务管理器的
  8.  
     
  9.  
    3. REQUIRED_NEW:内部的事务独立运行,在各自的作用域中,可以独立的回滚或者提交;
  10.  
    而外部的事务将不受内部事务的回滚状态影响。
  11.  
     
  12.  
    4. ESTED 的事务,基于单一的事务来管理,提供了多个保存点。
  13.  
    这种多个保存点的机制允许内部事务的变更触发外部事务的回滚。
  14.  
    而外部事务在混滚之后,仍能继续进行事务处理,即使部分操作已经被混滚。
  15.  
    由于这个设置基于 JDBC 的保存点,所以只能工作在 JDB C的机制。
  16.  
     
  17.  
    5. rollbackFor:让受检查异常回滚;即让本来不应该回滚的进行回滚操作。
  18.  
     
  19.  
    6. noRollbackFor:忽略非检查异常;即让本来应该回滚的不进行回滚操作。
  20.  
     

技术分享图片

技术分享图片技术分享图片

6.其他:

  1.  
    1. 事务方法的嵌套调用会产生事务传播。
  2.  
    2. spring 的事务管理是线程安全的
  3.  
    3. 父类的声明的 @Transactional 会对子类的所有方法进行事务增强;
  4.  
    子类覆盖重写父类方式可覆盖其 @Transactional 中的声明配置。
  5.  
     
  6.  
    4. 类名上方使用 @Transactional,类中方法可通过属性配置来覆盖类上的 @Transactional 配置;
  7.  
    比如:类上配置全局是可读写,可在某个方法上改为只读。

技术分享图片 

如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。 
如果不想终止,则必须捕获所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。


非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。

技术分享图片
--------------------- 

转自:https://blog.csdn.net/mingyundezuoan/article/details/79017659 

https://www.cnblogs.com/clwydjgs/p/9317849.html

 

 

 
 
 

spring-事务实现和@Transactional注解分析

原文:https://www.cnblogs.com/xuwc/p/13986565.html

(0)
(0)
   
举报
评论 一句话评论(0
分享档案
最新文章
教程昨日排行
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!