当前位置:   article > 正文

Spring声明式事务管理实现及原理详解_spring声明式事务实现原理

spring声明式事务实现原理

目录

1、实现步骤

1.1、配置事务管理器

1.2、启动事务注解

1.3、业务添加注解

2、代码演示

2.1、bean文件

2.2、目标类

2.3、测试类

3、Spring事务属性

3.1、传播行为

3.2、隔离级别

3.3、回滚属性

3.4、超时和只读

4、Spring事务回滚原则

5、Spring事务失效原因

5.1、数据库引擎

5.2、方法属性

5.3、抛出运行异常

5.4、启动事务注解

5.5、目标代理

5.6、同一线程

5.7、同类方法调用


1、实现步骤

1.1、配置事务管理器

         bean中添加一个数据源的事务管理器

1.2、启动事务注解

         导入tx命名空间,启用注解<tx:annotation-driven/>

1.3、业务添加注解

        在接口、接口方法、类以及类方法( public方法)上直接添加@Transactional即可。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。在方法上使用该注解会覆盖类上的定义。

        注意:虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但Spring团队建议:在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常

2、代码演示

2.1、bean文件

application_context.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:tx="http://www.springframework.org/schema/tx"
  5. xmlns:context="http://www.springframework.org/schema/context"
  6. xmlns:aop="http://www.springframework.org/schema/aop"
  7. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
  9. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
  11. <!-- 导入资源文件 -->
  12. <context:component-scan base-package="spring_transaction_annotation">
  13. </context:component-scan>
  14. <!-- 配置 C3P0 数据源 -->
  15. <bean id="dataSource"
  16. class ="com.mchange.v2.c3p0.ComboPooledDataSource">
  17. <property name="user" value="root"></property>
  18. <property name="password" value="111111"></property>
  19. <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  20. <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC"></property>
  21. <property name="initialPoolSize" value="5"></property>
  22. <property name="maxPoolSize" value ="110"></property>
  23. </bean>
  24. <!-- 配置 Spirng 的 JdbcTemplate -->
  25. <bean id="jt"
  26. class="org.springframework.jdbc.core.JdbcTemplate">
  27. <property name="dataSource" ref="dataSource"></property>
  28. </bean>
  29. <!-- 配置事务管理器 -->
  30. <bean id="transactionManager"
  31. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  32. <property name="dataSource" ref="dataSource"></property>
  33. </bean>
  34. <!-- 启用事务注解 -->
  35. <tx:annotation-driven transaction-manager="transactionManager"/>
  36. <!-- 开启暴露Aop代理到ThreadLocal支持 -->
  37. <aop:aspectj-autoproxy expose-proxy="true"/>
  38. </beans>

2.2、目标类

BankAccount.java:

  1. package spring_transaction_annotation;
  2. import org.springframework.aop.framework.AopContext;
  3. import org.springframework.aop.framework.AopProxy;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.jdbc.core.BeanPropertyRowMapper;
  6. import org.springframework.jdbc.core.JdbcTemplate;
  7. import org.springframework.jdbc.core.RowMapper;
  8. import org.springframework.stereotype.Component;
  9. import org.springframework.stereotype.Service;
  10. import org.springframework.transaction.annotation.Isolation;
  11. import org.springframework.transaction.annotation.Propagation;
  12. import org.springframework.transaction.annotation.Transactional;
  13. @Service("bankAccount")
  14. //@Transactional
  15. public class BankAccount {
  16. private int id;
  17. private String name;
  18. private double monery;
  19. @Autowired
  20. private JdbcTemplate jt;
  21. public int getId() {
  22. return id;
  23. }
  24. public void setId(int id) {
  25. this.id = id;
  26. }
  27. public String getName() {
  28. return name;
  29. }
  30. public void setName(String name) {
  31. this.name = name;
  32. }
  33. public double getMonery() {
  34. return monery;
  35. }
  36. public void setMonery(double monery) {
  37. this.monery = monery;
  38. }
  39. @Override
  40. public String toString() {
  41. return "BankAccount [id=" + id + ", name=" + name + ", monery=" + monery + "]";
  42. }
  43. public BankAccount getBankAccountEleByName(String name) {
  44. BankAccount ba=null;
  45. String str_cmd="select * from bank_account where name = ?";
  46. RowMapper<BankAccount> rm = new BeanPropertyRowMapper<>(BankAccount.class);
  47. ba = jt.queryForObject(str_cmd,rm,name);//类中的成员变量名和表中字段名相同方可正确解析
  48. return ba;
  49. }
  50. public void addBankAccountEle(BankAccount ele) {
  51. String str_cmd="insert into bank_account values(?,?,?)";
  52. jt.update(str_cmd, ele.getId(),ele.getName(),ele.getMonery());
  53. }
  54. public void modifyBankAccountEleMonery(String name,double monery,Boolean ModifyMode) {
  55. String query_str_cmd ="select monery from bank_account where name =?";
  56. double raw_monery = jt.queryForObject(query_str_cmd, double.class, name);
  57. String update_str_cmd ="update bank_account set monery = ? where name = ?";
  58. double update_monery =0;
  59. if(ModifyMode){
  60. update_monery = raw_monery + monery;
  61. }
  62. else{
  63. update_monery = raw_monery - monery;
  64. if(update_monery <0)
  65. {
  66. throw new MoneryException("余额不足!!!");
  67. }
  68. }
  69. jt.update(update_str_cmd, update_monery,name);
  70. }
  71. @Transactional(propagation=Propagation.REQUIRES_NEW,
  72. isolation=Isolation.READ_COMMITTED,
  73. readOnly=false,
  74. timeout=3
  75. )
  76. public void transferBankAccount(String src_name,String dst_name,int monery)
  77. {
  78. // try {
  79. // Thread.sleep(2000);
  80. // } catch (InterruptedException e) {}
  81. // new Thread(()->{
  82. //
  83. // modifyBankAccountEleMonery(dst_name, monery, true);
  84. // modifyBankAccountEleMonery(src_name,monery,false);
  85. //
  86. // }) .start();
  87. modifyBankAccountEleMonery(dst_name, monery, true);
  88. modifyBankAccountEleMonery(src_name,monery,false);
  89. }
  90. //类内部方法调用事务不生效
  91. public void callTransferBankAccount(String src_name,String dst_name,int monery)
  92. {
  93. // this.transferBankAccount(src_name,dst_name,monery);
  94. //手工调用AOP ((BankAccount)AopContext.currentProxy()).transferBankAccount(src_name,dst_name,monery);
  95. }
  96. }
  97. @Component("bankAccountTest")
  98. class BankAccountTest{
  99. @Autowired
  100. private BankAccount ba;
  101. //两次转账,测试事务的传播行为
  102. @Transactional
  103. public void twoTransferBankAccount(String src_name,String dst_name,int monery)
  104. {
  105. ba.transferBankAccount(src_name,dst_name,monery);//第一次金额充足
  106. ba.transferBankAccount(src_name,dst_name,monery);//第二次余额不足
  107. }
  108. }
  109. class MoneryException extends RuntimeException{
  110. public MoneryException() {
  111. super();
  112. // TODO Auto-generated constructor stub
  113. }
  114. public MoneryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
  115. super(message, cause, enableSuppression, writableStackTrace);
  116. // TODO Auto-generated constructor stub
  117. }
  118. public MoneryException(String message, Throwable cause) {
  119. super(message, cause);
  120. // TODO Auto-generated constructor stub
  121. }
  122. public MoneryException(String message) {
  123. super(message);
  124. // TODO Auto-generated constructor stub
  125. }
  126. public MoneryException(Throwable cause) {
  127. super(cause);
  128. // TODO Auto-generated constructor stub
  129. }
  130. }

2.3、测试类

TestMain.java:

  1. package spring_transaction_annotation;
  2. import java.sql.SQLException;
  3. import javax.sql.DataSource;
  4. import org.springframework.context.ApplicationContext;
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;
  6. public class TestMain {
  7. public static void main(String[] args) throws SQLException {
  8. ApplicationContext ctx = new ClassPathXmlApplicationContext("application_context.xml");
  9. BankAccount ba = (BankAccount) ctx.getBean("bankAccount");
  10. ba.transferBankAccount("xiaoming", "xiaohong", 100);
  11. }
  12. }

3、Spring事务属性

3.1、传播行为

       当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行. 事务的传播行为可以由传播属性指定. Spring 定义了 7  种类传播行为,具体如下:

前两种较为常用:REQUIRED使用调用方法的事务;REQUIRED_NEW启动新的事务

3.2、隔离级别

       从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行. 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行. 事务的隔离级别可以通过隔离事务属性指定,常用的隔离级别如下:

       事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持. Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE Mysql 支持 4 中事务隔离级别.

       用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别

3.3、回滚属性

        默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚, 而受检查异常不会。这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。事务的回滚规则可以通过 @Transactional 注解的 rollbackFor noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类。ps: rollbackFor = MoneryException.class ,...

3.4、超时和只读

       由于事务可以在行和表上获得锁,  因此长事务会占用资源, 并对整体性能产生影响. 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.

        超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.

        只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.

4、Spring事务回滚原则

      Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

       默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException及其子类以及Errors,抛出checked异常则不会导致事务回滚。可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。例如:使用@Transactional(rollbackFor=IOException.class)则抛出IOException异常时也可以回滚事务。也可以明确定义哪些异常抛出时不回滚事务,例如@Transactional(noRollbackFor = IOException.class)还可以编程性的通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法来手动地指示一个事务必须回滚

5、Spring事务失效原因

5.1、数据库引擎

       是否是数据库引擎设置不对造成的。比如我们最常用的mysql,引擎MyISAM,是不支持事务操作的。需要改成InnoDB才能支持

5.2、方法属性

       入口的方法必须是public,否则事务不起作用(这一点由Spring的AOP特性决定的,理论上而言,不public也能切入,但spring可能是觉得private自己用的方法,应该自己控制,不应该用事务切进去吧)。另外private 方法, final 方法 和 static 方法不能添加事务,加了也不生效

5.3、抛出运行异常

       Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚(至于为什么spring要这么设计:因为spring认为Checked的异常属于业务的,coder需要给出解决方案而不应该直接扔该框架)

问题:为什么捕获了异常,事务就不会回滚呢?

答:Spring的声明式事务管理是基于AOP实现的。

        具体原理如下:在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种。

       而Spring aop 异常捕获原理是被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚。捕获异常后,aop就发现不了异常,进而不能回滚。

5.4、启动事务注解

        xml配置方式中添加 <tx:annotation-driven />

5.5、目标代理

        请确认你的类是否被代理了(因为spring的事务实现原理为AOP,只有通过代理对象调用方法才能被拦截,事务才能生效)

目标方法被代理并正常拦截后,堆栈回溯包含有如下信息:

.....    

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:294)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689)

.....

5.6、同一线程

       请确保你的业务和事务入口在同一个线程里,否则事务也是不生效的,比如下面代码事务不生效:

  1. @Transactional
  2. public void transferBankAccount(String src_name,String dst_name,int monery)
  3. {
  4. new Thread(()->{
  5. modifyBankAccountEleMonery(dst_name, monery, true);
  6. modifyBankAccountEleMonery(src_name,monery,false);//余额不足,抛异常
  7. }) .start();
  8. }

5.7、同类方法调用

       同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的(这就是业界老问题:类内部方法调用事务不生效的问题原因),如下代码调用方法:callTransferBankAccount,事务不生效,原因:spring AOP失效

  1. @Transactional
  2. public void transferBankAccount(String src_name,String dst_name,int monery)
  3. {
  4. modifyBankAccountEleMonery(dst_name, monery, true);
  5. modifyBankAccountEleMonery(src_name,monery,false);
  6. }
  7. //类内部方法调用事务不生效
  8. public void callTransferBankAccount(String src_name,String dst_name,int monery)
  9. {
  10. transferBankAccount(src_name,dst_name,monery);
  11. //解决方法 //((BankAccount)AopContext.currentProxy()).transferBankAccount(src_name,dst_name,monery);
  12. }

解决方法:

1、在applicationContext.xml文件中配置如下:

  1. <!-- 开启暴露Aop代理到ThreadLocal支持 -->
  2. <aop:aspectj-autoproxy expose-proxy="true"/>

2、在需要切换的地方获取代理对象,再调用对应的方法,如下:

((类名) AopContext.currentProxy()).事务方法();  

3、注意,这里需要被代理对象使用的方法必须是public类型的方法,不然获取不到代理对象,会报下面的错误:

java.lang.IllegalStateException: Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.

开启暴露AOP代理即可.

       因为开启事务和事务回滚,实际这个过程是aop代理帮忙完成的,当调用一个方法时,它会先检查时候有事务,有则开启事务,当调用本类的方法是,它并没有将其视为proxy调用,而是方法的直接调用,所以也就没有检查该方法是否含有事务这个过程,那么本地方法调用的事务也就无效了。

具体原理分析:https://blog.csdn.net/jiesa/article/details/53438342

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/525756
推荐阅读
相关标签
  

闽ICP备14008679号