赞
踩
目录
bean中添加一个数据源的事务管理器
导入tx命名空间,启用注解<tx:annotation-driven/>
在接口、接口方法、类以及类方法( public方法)上直接添加@Transactional即可。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。在方法上使用该注解会覆盖类上的定义。
注意:虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但Spring团队建议:在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常
application_context.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:tx="http://www.springframework.org/schema/tx"
- 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.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
-
- <!-- 导入资源文件 -->
- <context:component-scan base-package="spring_transaction_annotation">
- </context:component-scan>
-
- <!-- 配置 C3P0 数据源 -->
- <bean id="dataSource"
- class ="com.mchange.v2.c3p0.ComboPooledDataSource">
-
- <property name="user" value="root"></property>
- <property name="password" value="111111"></property>
- <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
- <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC"></property>
- <property name="initialPoolSize" value="5"></property>
- <property name="maxPoolSize" value ="110"></property>
-
- </bean>
-
- <!-- 配置 Spirng 的 JdbcTemplate -->
- <bean id="jt"
- class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
-
- <!-- 配置事务管理器 -->
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource"></property>
- </bean>
-
- <!-- 启用事务注解 -->
- <tx:annotation-driven transaction-manager="transactionManager"/>
-
- <!-- 开启暴露Aop代理到ThreadLocal支持 -->
- <aop:aspectj-autoproxy expose-proxy="true"/>
-
- </beans>

BankAccount.java:
- package spring_transaction_annotation;
-
- import org.springframework.aop.framework.AopContext;
- import org.springframework.aop.framework.AopProxy;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jdbc.core.BeanPropertyRowMapper;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.jdbc.core.RowMapper;
- import org.springframework.stereotype.Component;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Isolation;
- import org.springframework.transaction.annotation.Propagation;
- import org.springframework.transaction.annotation.Transactional;
-
- @Service("bankAccount")
- //@Transactional
- public class BankAccount {
-
- private int id;
- private String name;
- private double monery;
- @Autowired
- private JdbcTemplate jt;
-
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public double getMonery() {
- return monery;
- }
- public void setMonery(double monery) {
- this.monery = monery;
- }
-
- @Override
- public String toString() {
- return "BankAccount [id=" + id + ", name=" + name + ", monery=" + monery + "]";
- }
-
- public BankAccount getBankAccountEleByName(String name) {
- BankAccount ba=null;
- String str_cmd="select * from bank_account where name = ?";
- RowMapper<BankAccount> rm = new BeanPropertyRowMapper<>(BankAccount.class);
- ba = jt.queryForObject(str_cmd,rm,name);//类中的成员变量名和表中字段名相同方可正确解析
- return ba;
- }
-
- public void addBankAccountEle(BankAccount ele) {
- String str_cmd="insert into bank_account values(?,?,?)";
- jt.update(str_cmd, ele.getId(),ele.getName(),ele.getMonery());
-
- }
-
- public void modifyBankAccountEleMonery(String name,double monery,Boolean ModifyMode) {
-
- String query_str_cmd ="select monery from bank_account where name =?";
- double raw_monery = jt.queryForObject(query_str_cmd, double.class, name);
- String update_str_cmd ="update bank_account set monery = ? where name = ?";
- double update_monery =0;
-
- if(ModifyMode){
- update_monery = raw_monery + monery;
- }
- else{
- update_monery = raw_monery - monery;
- if(update_monery <0)
- {
- throw new MoneryException("余额不足!!!");
- }
- }
-
- jt.update(update_str_cmd, update_monery,name);
- }
-
- @Transactional(propagation=Propagation.REQUIRES_NEW,
- isolation=Isolation.READ_COMMITTED,
- readOnly=false,
- timeout=3
- )
- public void transferBankAccount(String src_name,String dst_name,int monery)
- {
-
- // try {
- // Thread.sleep(2000);
- // } catch (InterruptedException e) {}
-
- // new Thread(()->{
- //
- // modifyBankAccountEleMonery(dst_name, monery, true);
- // modifyBankAccountEleMonery(src_name,monery,false);
- //
- // }) .start();
- modifyBankAccountEleMonery(dst_name, monery, true);
- modifyBankAccountEleMonery(src_name,monery,false);
- }
-
- //类内部方法调用事务不生效
- public void callTransferBankAccount(String src_name,String dst_name,int monery)
- {
- // this.transferBankAccount(src_name,dst_name,monery);
- //手工调用AOP ((BankAccount)AopContext.currentProxy()).transferBankAccount(src_name,dst_name,monery);
- }
- }
-
- @Component("bankAccountTest")
- class BankAccountTest{
-
- @Autowired
- private BankAccount ba;
-
- //两次转账,测试事务的传播行为
- @Transactional
- public void twoTransferBankAccount(String src_name,String dst_name,int monery)
- {
- ba.transferBankAccount(src_name,dst_name,monery);//第一次金额充足
- ba.transferBankAccount(src_name,dst_name,monery);//第二次余额不足
- }
- }
-
-
- class MoneryException extends RuntimeException{
-
- public MoneryException() {
- super();
- // TODO Auto-generated constructor stub
- }
-
- public MoneryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
- super(message, cause, enableSuppression, writableStackTrace);
- // TODO Auto-generated constructor stub
- }
-
- public MoneryException(String message, Throwable cause) {
- super(message, cause);
- // TODO Auto-generated constructor stub
- }
-
- public MoneryException(String message) {
- super(message);
- // TODO Auto-generated constructor stub
- }
-
- public MoneryException(Throwable cause) {
- super(cause);
- // TODO Auto-generated constructor stub
- }
-
- }

TestMain.java:
- package spring_transaction_annotation;
-
- import java.sql.SQLException;
-
- import javax.sql.DataSource;
-
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- public class TestMain {
- public static void main(String[] args) throws SQLException {
- ApplicationContext ctx = new ClassPathXmlApplicationContext("application_context.xml");
- BankAccount ba = (BankAccount) ctx.getBean("bankAccount");
- ba.transferBankAccount("xiaoming", "xiaohong", 100);
- }
- }

当事务方法被另一个事务方法调用时, 必须指定事务应该如何传播. 例如: 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行. 事务的传播行为可以由传播属性指定. Spring 定义了 7 种类传播行为,具体如下:
前两种较为常用:REQUIRED使用调用方法的事务;REQUIRED_NEW启动新的事务
从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题. 然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行. 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行. 事务的隔离级别可以通过隔离事务属性指定,常用的隔离级别如下:
事务的隔离级别要得到底层数据库引擎的支持, 而不是应用程序或者框架的支持. Oracle 支持的 2 种事务隔离级别:READ_COMMITED , SERIALIZABLE Mysql 支持 4 中事务隔离级别.
用 @Transactional 注解声明式地管理事务时可以在 @Transactional 的 isolation 属性中设置隔离级别
默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚, 而受检查异常不会。这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义. 这两个属性被声明为 Class[] 类型的, 因此可以为这两个属性指定多个异常类。ps: rollbackFor = MoneryException.class ,...
由于事务可以在行和表上获得锁, 因此长事务会占用资源, 并对整体性能产生影响. 如果一个事物只读取数据但不做修改, 数据库引擎可以对这个事务进行优化.
超时事务属性: 事务在强制回滚之前可以保持多久. 这样可以防止长期运行的事务占用资源.
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务.
Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。
默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException及其子类以及Errors,抛出checked异常则不会导致事务回滚。可以明确的配置在抛出哪些异常时回滚事务,包括checked异常。例如:使用@Transactional(rollbackFor=IOException.class)则抛出IOException异常时也可以回滚事务。也可以明确定义哪些异常抛出时不回滚事务,例如@Transactional(noRollbackFor = IOException.class)还可以编程性的通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法来手动地指示一个事务必须回滚
是否是数据库引擎设置不对造成的。比如我们最常用的mysql,引擎MyISAM,是不支持事务操作的。需要改成InnoDB才能支持
入口的方法必须是public,否则事务不起作用(这一点由Spring的AOP特性决定的,理论上而言,不public也能切入,但spring可能是觉得private自己用的方法,应该自己控制,不应该用事务切进去吧)。另外private 方法, final 方法 和 static 方法不能添加事务,加了也不生效
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就发现不了异常,进而不能回滚。
xml配置方式中添加 <tx:annotation-driven />
请确认你的类是否被代理了(因为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)
.....
请确保你的业务和事务入口在同一个线程里,否则事务也是不生效的,比如下面代码事务不生效:
- @Transactional
- public void transferBankAccount(String src_name,String dst_name,int monery)
- {
-
- new Thread(()->{
- modifyBankAccountEleMonery(dst_name, monery, true);
- modifyBankAccountEleMonery(src_name,monery,false);//余额不足,抛异常
- }) .start();
-
- }
同一个类中一个无事务的方法调用另一个有事务的方法,事务是不会起作用的(这就是业界老问题:类内部方法调用事务不生效的问题原因),如下代码调用方法:callTransferBankAccount,事务不生效,原因:spring AOP失效
- @Transactional
- public void transferBankAccount(String src_name,String dst_name,int monery)
- {
-
- modifyBankAccountEleMonery(dst_name, monery, true);
- modifyBankAccountEleMonery(src_name,monery,false);
- }
-
- //类内部方法调用事务不生效
- public void callTransferBankAccount(String src_name,String dst_name,int monery)
- {
- transferBankAccount(src_name,dst_name,monery);
- //解决方法 //((BankAccount)AopContext.currentProxy()).transferBankAccount(src_name,dst_name,monery);
- }
解决方法:
1、在applicationContext.xml文件中配置如下:
- <!-- 开启暴露Aop代理到ThreadLocal支持 -->
- <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调用,而是方法的直接调用,所以也就没有检查该方法是否含有事务这个过程,那么本地方法调用的事务也就无效了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。