基于注解的 AOP 实现事务控制
# 130.基于注解的 AOP 实现事务控制
我们改造上一篇博客的案例,改为使用注解的方式
# 配置 bean.xml
由于我们使用了注解,因此得加上注解的约束:添加了第 5,10,11 行
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
2
3
4
5
6
7
8
9
10
11
12
我们首先配置容器创建时要扫描的包:
<context:component-scan base-package="com.peterjxl"/>
# 修改 service 类
在 service 实现类中,加上@Service 的注解,并给 dao 加上 @Autowired
注解,去掉 set 方法
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
2
3
4
5
然后我们删除 bean.xml 中的配置
# 配置 dao
dao 实现类同理,加上@Repository 注解,并给成员变量加上 @Autowired
注解,去掉 set 方法
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Autowired
private ConnectionUtils connectionUtils;
2
3
4
5
6
7
8
然后我们删除 bean.xml 中的配置
# 配置 ConnectionUtils
加上@Compoment 注解,并给 dataSource 加上注解,去掉 set 方法
@Component("connectionUtils")
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
@Autowired
private DataSource dataSource;
2
3
4
5
6
7
然后我们删除 bean.xml 中的配置
# 配置 TransactionManager
我们做如下事情
- 给类加上 Component 的注解,和@Aspect 的注解
- 配置切入点表达式
- 给各个方法配置通知
完整代码:
package com.peterjxl.utils;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component("transactionManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* com.peterjxl.service.impl.*.*(..))")
private void pt1() {}
@Before("pt1()")
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@AfterReturning("pt1()")
public void commit() {
try {
connectionUtils.getThreadConnection().commit();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@AfterThrowing("pt1()")
public void rollback() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@After("pt1()")
public void release() {
try {
connectionUtils.getThreadConnection().close(); // 还回连接池中
connectionUtils.removeConnection();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
配置 bean.xml,我们删除其 bean 配置,和 AOP 的配置,然后加上
<aop:aspectj-autoproxy/>
# 测试
此时我们就已经用注解改造完了,完整的配置如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.peterjxl"/>
<aop:aspectj-autoproxy/>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/learnSpring"/>
<property name="user" value="learnSpringUser"/>
<property name="password" value="learnSpringPassword"/>
</bean>
</beans>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
然后我们测试下,有异常的情况下能否正常运行,运行结果:
java.lang.RuntimeException: java.sql.SQLNonTransientConnectionException: Can't call rollback when autocommit=true
这是因为使用注解的时候,执行顺序是有问题的,有异常时先执行了最终通知,也就是执行了 release 方法释放了连接,然后再回滚,是会出错的
即使没有异常,提交也是不行的,因为提交的时候先执行了最终通知,释放了连接,然后 commit,当然也是不行的
综上,不管转账时有无异常,数据库数据都没有变化,因为既没有提交,也没有回滚。只能用环绕通知
# 使用环绕通知
我们将其他方法上面的注解注释掉,然后在 TransactionManager
新加一个方法:
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp) {
Object rtValue = null;
try {
// 1. 获取参数
Object[] args = pjp.getArgs();
// 2. 开启事务
this.beginTransaction();
// 3. 执行方法
rtValue = pjp.proceed(args);
// 4. 提交事务
this.commit();
// 5. 返回结果
return rtValue;
} catch (Throwable e) {
// 6. 回滚事务
this.rollback();
throw new RuntimeException(e);
} finally {
// 7. 释放资源
this.release();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
此时我们再进行测试,能看到正常控制事务
# 源码
本项目已将源码上传到 GitHub (opens new window) 和 Gitee (opens new window) 上。并且创建了分支 demo16,读者可以通过切换分支来查看本文的示例代码