Spring AOP和事务
一 . Spring AOP
(一).Spring AOP的介绍
1-Spring AOP是什么?
AOP(Aspect Oriented Programming),意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。
Java是一个面向对象(OOP)的编程语言,但它有个弊端就是当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为,这样做不便于维护,而且有大量重复代码,AOP的出现弥补了OOP的这点不足。
2-Spring AOP有什么用?
-
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
-
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务进行增强。
3-Spring AOP的优点
AOP利用一种称为**“横切**”的技术,剖开对象内部,但无需侵入到业务代码中,且将公共行为封装到可重用模块,从而减少了重复代码,使业务与非业务处理逻辑分离,降低耦合,提高开发效率,并且便于维护。
(二).Spring AOP的核心概念
-
Joinpoint(连接点):指在应用执行过程中满足切点范围的具体的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
-
Pointcut(切点):指切面插入在哪些方法上,确定切面使用范围。
-
Advice(通知):定义了切点处所要执行的程序代码以及执行时机(通知分类如下)。
-
before(前置通知):通知方法在目标方法调用之前执行
-
after(后置通知):通知方法在目标方法返回或异常后调用
-
after-returning(返回后通知):通知方法会在目标方法返回后调用
-
after-throwing(抛出异常通知):通知方法会在目标方法抛出异常后调用
-
around(环绕通知):通知方法在目标前后都执行,及环绕通知方法包含上面四种通知方法,功能最全面
-
-
Target(目标):指代理的目标对象。
-
Weaving(织入):指把切面插入到目标对象上,生成代理对象的过程。
-
Proxy(代理):指生成代理的对象。
-
Aspect(切面):是指封装横切到系统功能的类,包含通知和切点。
(三).Spring AOP的实现
1.基于配置文件方式实现
(1)导入AOP相关坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
(2)编写核心业务代码
在com.cqgcxy.service包下创建目标接口TestService(目标类的目标方法)
public interface TestService {
void select();
}
在com.cqgcxy.service.impl包下创建目标类TestServiceImpl
@Service
public class TestServiceImpl implements TestService {
public void select() {
System.out.println("Hello,world!");
}
}
(3)编写切面类
切面类中有通知(增强功能方法): 新增一个日志记录服务类“LogService”,并配置到容器中
//记录日志
@Service
public class LogService(){
public void writeLog(JoinPoint point) {
//从连接点获取方法名
String methodName=point.getSignature().getName();
System.out.println("开始执行 "+methodName+" 方法");
System.out.println("开始时间:" + new Date());
//从连接点获取参数
Object[] args=point.getArgs();
System.out.print("传递参数信息:");
for (Object arg : args) {
System.out.print(arg.toString()+",");
}
System.out.println("");
}
public void writeEndLog(JoinPoint point,Object val) {
String methodName=point.getSignature().getName();
System.out.println("结束执行 "+methodName+" 方法");
System.out.println("结束时间:" + new Date());
System.out.println("方法返回值:"+val.toString());
}
public void writeErrorLog(JoinPoint point,Exception exception){
String methodName=point.getSignature().getName();
System.out.println(methodName+"方法出现异常");
System.out.println("捕捉到异常,异常信息:"+exception.getMessage());
}
}
*若未使用注解@Service装配,需手动配置到容器中
<bean id="logService" class="com.cqgcxy.aop.LogService"></bean>
<bean id="testService" class="com.cqgcxy.service.impl.TestServiceImpl"></bean>
(4)修改Spring配置文件
修改Spring配置文件(applicationContext.xml)的头部(因为要用到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"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
(5)加入AOP切面配置
在Spring配置文件(applicationContext.xml)中加入AOP切面配置
-
方式一:
<!-- AOP配置根节点 --> <aop:config> <!-- 定义切面 --> <aop:aspect ref="logService"> <!-- 定义切点 --> <aop:pointcut expression="execution(* com.cqgcxy.service.*.*(..))" id="pointcut" /> <!-- 定义前置通知 --> <aop:before pointcut-ref="pointcut" method="writeLog" /> </aop:aspect> </aop:config>
-
方式二
<!-- AOP配置根节点 --> <aop:config> <!-- 定义切面 --> <aop:aspect ref="logService"> <!-- 定义前置通知和切点 --> <aop:before method="writeLog" pointcut="execution(* com.cqgcxy.service..*.*(..))" /> </aop:aspect> </aop:config>
说明:
1、定义通知类bean
2、通过aop:config标签配置所有的切面
3、通过aop:pointcut标签配置切点,使用切点表达式来指定范围
4、通过aop:aspect定义切面,指定引用的通知以及通知的类型和切点
切点表达式的语法
execution([修饰符]返回值类型包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号*代表任意
- 包名与类名之间一个点.代表当前包下的类,两个点…表示当前包及其子包下的类
- 参数列表可以使用两个点…表示任意个数,任意类型的参数列表
execution(public void com.iflytek.aop.Target.method())
execution(void com.iflytek.aop.Target.* ( ..))
execution(* com.iflytek.aop.*.*( ..))
execution(* com.iflytek.aop..*.* (..))
execution(* *..*.*(..))
通知的配置语法
<!--配置文件方式-->
<aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
//注解方式
@通知注解("切点表达式")
名称 | 标签 | 注解 | 说明 |
---|---|---|---|
前置通知 | <aop:before> | @Before | 用于配置前置通知。指定增强的方法在切入点方法之前执行 |
后置通知 | <aop:AfterReturning> | @AfterReturning | 用于配置后置通知。指定增强的方法在切入点方法之后执行 |
环绕通知 | <aop:Around> | @Around | 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行 |
异常抛出通知 | <aop:AfterThrowing> | @AfterThrowing | 用于配置异常抛出通知。指定增强的方法在出现异常时执行 |
最终通知 | <aop:After> | @After | 用于配置最终通知。无论增强方式执行是否有异常都会执行 |
说明:
1、前四种通知的方法里面可以通过JoinPoint参数获取连接点信息,比如参数信息等,方便进行处理
public void writeLog(JoinPoint point) {
//从连接点获取方法名
String methodName=point.getSignature().getName();
System.out.println("开始执行 "+methodName+" 方法");
System.out.println("开始时间:" + new Date());
//从连接点获取参数
Object[] args=point.getArgs();
System.out.print("传递参数信息:");
for (Object arg : args) {
System.out.print(arg.toString()+",");
}
System.out.println("");
}
2、环绕通知的方法里通过ProceedingJoinPoint参数来获取连接点信息
public Object printLog(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();//signature包含了关于方法的一些信息,如方法名、类名、参数类型等
System.out.println(LocalDateTime.now()+"==============>"+signature+"===============>开始执行!");
Object proceed = pjp.proceed();//执行了被切点的方法(即目标方法)
System.out.println(LocalDateTime.now()+"==============>"+signature+"===============>结束执行!");
return proceed;
}
(6)测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestServiceImplTest {
@Autowired
private TestService TesteService;
@Test
public void select() {
TesteService.select();
}
}
2.基于注解方式实现
(1)导入AOP相关坐标
同上
(2)编写核心业务代码
同上
(3)开启aop注解方式
在spring配置文件中开启aop注解方式
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(4)给切面类添加注解
使用**@Aspect定义切面,使用@Pointcut定义切点,使用@Before**、@After、@Around、@AfterReturning、@AfterThrowing定义通知的应用时机
方式一:
//日志服务
@Component
@Aspect
public class LogService {
@Pointcut("execution(* com.cqgcxy.service.*.*(..))")
public void pointcut(){}
@Before("LogService.pointcut()")
private void writeLog(JoinPoint point) {
//从连接点获取方法名
String methodName=point.getSignature().getName();
//从连接点获取参数
Object[] args=point.getArgs();
System.out.println("开始执行service,时间:" + new Date().toLocaleString());
System.out.println("service方法名:"+methodName);
System.out.print("传递参数信息:");
for (Object arg : args) {
System.out.print(arg.toString()+",");
}
System.out.println("");
}
@AfterReturning(value="pointcut()",returning="returnVal")
private void writeEndLog(JoinPoint point,Object returnVal) {
System.out.println("结束执行service,时间:" + new Date().toLocaleString());
System.out.println("方法返回值:"+returnVal.toString());
}
}
方式二:
//日志服务
@Component
@Aspect
public class LogService {
@Before("execution(* com.cqgcxy.service.*.*(..))")
private void writeLog(JoinPoint point) {
//从连接点获取方法名
String methodName=point.getSignature().getName();
//从连接点获取参数
Object[] args=point.getArgs();
System.out.println("开始执行service,时间:" + new Date().toLocaleString());
System.out.println("service方法名:"+methodName);
System.out.print("传递参数信息:");
for (Object arg : args) {
System.out.print(arg.toString()+",");
}
System.out.println("");
}
}
(5)添加配置类
将目标类和切面类的对象创建权交给spring,并在配置文件中开启组件扫描和AOP的自动代理
@Configuration
@ComponentScan("com.cqgcxy")
@EnableAspectJAutoProxy
public class SpringConfiguration{
}
(6)测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestServiceImplTest {
@Autowired
private TestService TesteService;
@Test
public void select() {
TesteService.select();
}
}
二. Spring事务管理
(一).Spring 事务的介绍
1-Spring事务管理是什么?
Spring AOP是通过动态代理来实现,主要是JDK动态代理和CGLIB代理两种代理模式 :
-
JDK动态代理:Spring AOP的首选方法。 每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
-
CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。
2-Spring事务管理有什么用?
在实际开发中,操作数据库时都会涉及到事务管理问题,为此Spring提供了专门用于事务处理的API。Spring的事务管理简化了传统的事务管理流程,并且在一定程度上减少了开发者的工作量。
3-JDK动态代理的优缺点
-
优点
JDK动态代理是JDK原生的,不需要任何依赖即可使用;
通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;
-
缺点
如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;
JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。
JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;
4-事务管理的核心接口
(1).Platform TransactionManager
PlatformTransactionManager接口是Spring提供的平台事务管理器,主要用于管理事务。该接口中提供了三个事务操作的方法,具体如下:
TransactionStatus getTransaction(TransactionDefinition definition);//用于获取事务以及状态信息
void commit(TransactionStatus status);//用于提交事务
void rollback(TransactionStatus status);//用于回滚事务
PlatformTransactionManager接口只是代表事务管理的接口,并不知道底层是如何管理事务的,具体如何管理事务则由它的实现类来完成。该接口常见的几个实现类如下:
注 : 当底层采用不同的持久层技术时,系统只需使用不同的PlatformTransactionManager实现类即可。
(2).TransactionDefinition
TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务基本属性,并提供了获取事务基本属性的方法,具体如下:
String getName( ); //获取事务对象名称
int getIsolationLevel( ); //获取事务的隔离级别
int getPropagationBehavior( ); //获取事务的传播行为
int getTimeout( ); //获取事务的超时时间
boolean isReadOnly( ); //获取事务是否只读
传播行为(propagation behavior):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。Spring定义了七种传播行为:
1.ROPAGATION_REQUIRED : 支持当前事务,如果不存在就新建一个
-
PROPAGATION_SUPPORTS : 支持当前事务,如果不存在,就不使用事务
-
PROPAGATION_MANDATORY : 支持当前事务,如果不存在,抛出异常
-
PROPAGATION_REQUIRES_NEW : 如果有事务存在,挂起当前事务,创建一个新的事务
-
PROPAGATION_NOT_SUPPORTED : 以非事务方式运行,如果有事务存在,挂起当前事务
-
PROPAGATION_NEVER : 以非事务运行,如果有事务存在,抛出异常
-
PROPAGATION_NESTED : 如果当前事务存在,则嵌套事务执行
隔离级别(isolation leve):定义了一个事务可能受其他并发事务的影响程度。多个事务并发运行,经常会操作相同的数据来完成各自的任务,可能会出现脏读,不可重复读和幻读的问题。隔离级别有四种:
-
DEFAULT : 使用后端数据库默认的隔离级别(spring中的选择项)
-
READ_UNCOMMITTED : 允许你读取还没有提交的改变了的数据,可能会导致脏读、幻读、不可重复读
-
READ_COMMITTED : 允许在并发事务已经提交后读取数据,可以防止脏读,但幻读和不可重复读仍可能发生
-
REPEATABLE_READ : 对相同字段的多次读取是一致的,除非数据被事务本身改变。可以防止脏读、不可重复读,但是幻读仍可能发生
-
SERIALIZABLE : 最高隔离级别,它完全服从ACID的隔离级别,确保不发生脏读、幻读、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的
是否只读(isReadOnly):如果一个方法内都是对数据库的select操作,那么可以设置方法事务为只读,数据库也会对该事务进行特定的优化。只读事务内不能有insert、update、delete的操作
事务超时(timeout):事务可能设计对后端数据库的锁定,所以长时间的事务运行会不必要的占用数据库资源,设置事务超时时间可以及时释放资源
(3).TransactionStatus
TransactionStatus接口是事务的状态,它描述了某一时间点上事务的状态信息。该接口中包含6个方法,具体如下:
void flush(); //刷新事务
boolean hasSavepoint(); //获取是否存在保存点
boolean isCompleted(); //获取事务是否完成
boolean isNewTransaction(); //获取是否为新事务
boolean isRollbackOnly(); //获取事务是否回滚
void setRollbackOnly(); //设置事务回滚
(二).Spring事务管理分两种方式
1-编程式事务管理
通过编写代码实现的事务管理 , 包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚
(1)导入AOP相关坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
</dependencies>
(2).添加事务管理器组件
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
(3).在业务逻辑类中使用事务管理器
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
@Autowired
private PlatformTransactionManager txManager;
//转账操作方法
public String moneyTransfer(String accountA, String accountB, double money) {
System.out.println("开始转账");
// 获取A和B账户的详情
Account A = accountDao.selectAccount(accountA);
Account B = accountDao.selectAccount(accountB);
// //定义事务规则(隔离级别、传播行为)
DefaultTransactionDefinition definition=new DefaultTransactionDefinition();
// //开启事务管理,并返回事务状态
TransactionStatus status = txManager.getTransaction(definition);
try {
// A账户减少相应金额
accountDao.updateMoney(accountA, A.getMoney() - money);
// B账户增加相应金额
int i=1/0;
accountDao.updateMoney(accountB, B.getMoney() + money);
txManager.commit(status); //提交事务
return "转账成功";
} catch (Exception e) {
txManager.rollback(status); //如果出现异常回滚事务
return "转账失败"+e.getLocalizedMessage();
}
}
}
(5).在方法中编程实现事务管理
在moneyTransfer方法中,使用事务管理器编写代码事务的提交或回滚
//定义事务规则(隔离级别、传播行为)
DefaultTransactionDefinition definition=new DefaultTransactionDefinition();
//开启事务管理,并返回事务状态
TransactionStatus status =txManager.getTransaction(definition);
try {
//转账业务逻辑(省略)
txManager.commit(status); //提交事务
} catch (Exception e) {
txManager.rollback(status); //如果出现异常回滚事务
}
(6).测试
public class AccountTest {
@Test
public void moneyTransfer(){
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service= context.getBean(AccountService.class);
String accountA="0102030";
String accountB="0103040";
String result= service.moneyTransfer(accountA, accountB, 1000);
System.out.println(result);
}
}
(7).编程式事务管理缺点
-
编程式事务管理必须要在业务逻辑中包含额外的事务管理代码。和业务逻辑代码产生了耦合,产生了代码冗余,不方便代码的维护和扩展。
-
声明式事务管理最大的优点在于开发者无需通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务应用到业务逻辑中。这使得开发人员可以更加专注于核心业务逻辑代码的编写,在一定程度上减少了工作量,提高了开发效率,所以在实际开发中,通常都推荐使用声明式事务管理。
3-声明式事务管理
通过XML配置或者注解的方式实现的事务管理 。
原理:通过AOP技术实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中
(1).基于XML
基于XML方式的声明式事务是在配置文件中通过tx:advice元素配置事务规则来实现的,然后通过使用aop:config编写的AOP配置,让Spring自动对目标生成代理。tx:advice元素及其子元素如下图所示:
配置tx:advice元素的重点是配置tx:method子元素,上图中使用灰色标注的几个属性是tx:method元素中的常用属性。其属性描述具体如下:
1)-添加事务管理器组件
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
2)-使用tx:advice标签配置事务规则
①因为要用到tx标签配置事务,需要修改applicationContext.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"
xmlns:tx="http://www.springframework.org/schema/tx"
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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
②使用tx:advice标签进一步对事务管理器配置事务规则
<!-- 配置事务规则 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 定义哪些方法需要进行事务处理,*表示任意字符 -->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="moneyTransfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
2)-使用aop:config配置事务切面
使用aop:config标签配置事务切面,并应用事务规则
<!-- 定义切面 -->
<aop:config>
<!-- 定义切点范围 -->
<aop:pointcut expression="execution(* com.cqgcxy.service.*.*(..))" id="pointcut"/>
<!-- 应用事务规则 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
(2).基于注解
1).配置事务管理器
在applicationContext.xml中配置一个事务管理器组件,提供对事务处理的全面支持和统一管理
<!--定义事务管理器 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
2).注册事务注解驱动
在applicationContext.xml中添加对注解配置事务的支持
<tx:annotation-driven transaction-manager=“txManager"/>
3).添加@Transactional注解
在需要事务管理的类或方法上使用@Transactional注解
如果将注解添加在Bean类上,则表示事务的设置对整个Bean类的所有方法都起作用;
如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。
@Transactional
public String moneyTransfer(String accountA, String accountB, double money) {
@Autowired
private AccountDao accountDao;
//业务逻辑代码
@Transactional(propagation= Propagation.REQUIRED,rollbackFor=Exception.class)
public String moneyTransfer(String accountA, String accountB, double money) {
System.out.println("开始转账");
// 获取A和B账户的详情
Account A = accountDao.selectAccount(accountA);
Account B = accountDao.selectAccount(accountB);
// A账户减少相应金额
accountDao.updateMoney(accountA, A.getMoney() - money);
// B账户增加相应金额
int i=1/0;
accountDao.updateMoney(accountB, B.getMoney() + money);
return "转账成功";
}
}
使用@Transactional注解时,可以通过参数配置事务详情:
.添加@Transactional注解
在需要事务管理的类或方法上使用@Transactional注解
如果将注解添加在Bean类上,则表示事务的设置对整个Bean类的所有方法都起作用;
如果将注解添加在Bean类中的某个方法上,则表示事务的设置只对该方法有效。
@Transactional
public String moneyTransfer(String accountA, String accountB, double money) {
@Autowired
private AccountDao accountDao;
//业务逻辑代码
@Transactional(propagation= Propagation.REQUIRED,rollbackFor=Exception.class)
public String moneyTransfer(String accountA, String accountB, double money) {
System.out.println("开始转账");
// 获取A和B账户的详情
Account A = accountDao.selectAccount(accountA);
Account B = accountDao.selectAccount(accountB);
// A账户减少相应金额
accountDao.updateMoney(accountA, A.getMoney() - money);
// B账户增加相应金额
int i=1/0;
accountDao.updateMoney(accountB, B.getMoney() + money);
return "转账成功";
}
}
使用@Transactional注解时,可以通过参数配置事务详情: