代码地址
SpringAop
觉得博主还可以给个Star
前面我们进行了基础版本的简介,主要是五个通知。
接下来我们应用到实际场景(事务管理):
pom.xml
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
MsgMapper.java
package com.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface MsgMapper {
@Select("INSERT INTO msg VALUES(NULL,'long',1);")
void insert();
}
MsgService.java
package com.service;
import com.mapper.MsgMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MsgService {
@Autowired
MsgMapper mapper;
public void insert(){
System.out.println("方法执行中");
mapper.insert();
}
}
MsgController.java
package com.web;
import com.service.MsgService;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MsgController {
@Autowired
MsgService msgService;
@RequestMapping(value = "/test",method = RequestMethod.GET)
public String test( int j){
msgService.insert();
return "success";
}
}
Application.java
package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
我们利用最基础的ssm框架来进行模拟事务管理的场景
我们都知道一个叫做事务回滚。
事务回滚:事务是一组组合成逻辑工作单元的操作,虽然系统中可能会出错,但事务将控制和维护事务中每个操作的一致性和完整性。一旦事务中某个逻辑出错了,我们将会撤回之前所有做的一切。
ssm可以说是很好的代表,在我们进行数据库操作的过程中,假如第一步进行数据存入,而第二步将第一步的数据做一定处理,然后再交给第三步进行数据库存储,而第一步和第三步必要有对应关系。在这一种情况之下,我们就必须保证第二步的处理不能出错,一旦出错,就没有了第三步,对应关系也没有了。所以我们要把这三步包在一个事务内,一旦某个步骤出错了,我们可以回滚,就避免了没有对应关系的问题。
好了,说了这么多,用代码来解释吧。
先运行以上代码。
运行成功,访问http://127.0.0.1:8080/test?j=1
数据库数据增加了一条
好了,这是正常现象,那么我在MsgService里面的方法加上一个会报错的运算。
这么一看,我们知道在运算的时候一定会报错,但是数据还是会插入到数据库
重新运行,并访问http://127.0.0.1:8080/test?j=1
我们发现数据还是插入进去了,但是这个数据在理论逻辑是没有插入的,这时候我们就需要用到事务回滚了。
- @Transactional
修改MsgService.java
package com.service;
import com.mapper.MsgMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MsgService {
@Autowired
MsgMapper mapper;
@Transactional
public void insert(int j){
System.out.println("方法执行中");
mapper.insert();
int i = j / 0;
}
}
我们直接加上注解,重新运行,并访问http://127.0.0.1:8080/test?j=1
数据库中没有增加数据。显然,我们达到了事务回滚的效果。
- DataSourceTransactionManager
DataSourceTransactionManager这种实现方法需要手写,我们来手写这个事务吧
创建TransactionUtil.java
package com.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
@Component
public class TransactionUtil {
/**
* 获取当前事务管理器
*/
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
public TransactionStatus begin(){
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
return transaction;
}
/**
* 提交事务
* @param transactionStatus
*/
public void commit(TransactionStatus transactionStatus){
dataSourceTransactionManager.commit(transactionStatus);
}
/**
* 回滚事务
*/
public void rollback(TransactionStatus transactionStatus){
dataSourceTransactionManager.rollback(transactionStatus);
}
}
MsgService.java代码再次修改
package com.service;
import com.mapper.MsgMapper;
import com.util.TransactionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
@Service
public class MsgService {
@Autowired
MsgMapper mapper;
@Autowired
TransactionUtil transactionUtil;
public void insert(int j){
TransactionStatus transactionStatus = transactionUtil.begin();
try{
System.out.println("方法执行中");
mapper.insert();
int i = j / 0;
transactionUtil.commit(transactionStatus);
}catch (Exception e){
transactionUtil.rollback(transactionStatus);
}
}
}
一样可以实现。但是问题来了,我们以上两种都是针对于一个方法,那如果方法多了,不可能我们每次都加注解或者手写一次,不太现实,我们回到基础版说的,减少冗余代码。对,我们还可以利用aop来完成事务回滚
3. AOP实现事务回滚
创建 TransactionAop.java
package com.aop;
import com.util.TransactionUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
@Aspect
@Component
@EnableAspectJAutoProxy
public class TransactionAop{
@Autowired
TransactionUtil transactionUtil;
@Pointcut("execution(* com.service..*.*(..))")
public void transactionAop(){
}
@Around("transactionAop()")
public void doAround(ProceedingJoinPoint joinPoint) throws Throwable {
TransactionStatus transactionStatus = transactionUtil.begin();
try{
joinPoint.proceed();
transactionUtil.commit(transactionStatus);
}catch (Exception e){
transactionUtil.rollback(transactionStatus);
}
}
}
这样就实现了,针对所有的方法,但是。我们知道bean的创建,默认是单例。如果是多个方法使用一个单例,那么就会产生阻塞,那么效率会变得很慢。所以我们需要把TransactionAop变成多例,加上注解@Scope(“prototype”)即可。但还有疑问,那为什么不加在TransactionUtil上,把TransactionUtil变成多例的?因为TransactionUtil在TransactionAop中,一个单例中存在多例,那么这个多例会失效,实质上还是单例。
还有着一个界面问题,我们现在会发现,运算异常的存在,导致我们的界面一直500,处理方法,更改TransactionAop的doAround方法。如下
@Around("transactionAop()")
public Integer doAround(ProceedingJoinPoint joinPoint) throws Throwable {
TransactionStatus transactionStatus = transactionUtil.begin();
try{
joinPoint.proceed();
transactionUtil.commit(transactionStatus);
return 1;
}catch (Exception e){
transactionUtil.rollback(transactionStatus);
return 0;
}
}
还有一种情况,不想交给AOP处理,我们可以利用手动回滚事务
修改MsgService.java中的insert方法
public void insert(int j){
try{
System.out.println("方法执行中");
mapper.insert();
int i = j / 0;
}catch (Exception e){
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
这里要注意,在此方法中捕捉了异常,那么在TransactionAop是捕捉不到异常的,所以TransactionAop中的回滚并未执行。