我们利用现有的AOP来实现事务的管理,数据库访问我们直接使用jdbc,首先明确两点:
切点应该如何定义?
通知要实现什么功能?
我们先说第一个问题,因为是我们自己模拟,所以关于切点的定义我们就设置的尽量简单一些,不妨就直接指定某个包下的所有类。对于第二个问题,我们也不做的过于复杂,在方法执行前开启事务,在方法执行后提交事务并关闭连接,所以我们需要定义一个环绕通知。同时,我们也需要将连接跟事务同步,保证事务中的所有SQL共用一个事务是实现事务管理的必要条件。基于此,我们开始编写代码
我们只需要引入Spring相关的依赖跟JDBC相关依赖即可,该项目仅仅是一个Spring环境下的Java项目,没有Web依赖,也不是SpringBoot项目,项目结构如下:
POM文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dmz.framework</groupId>
<artifactId>mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
</project>
配置类:
// 开启AOP跟扫描组件即可
@EnableAspectJAutoProxy
@ComponentScan("com.dmz.mybatis.tx_demo")
public class Config {
}
完成事务管理的核心类:
public class TransactionUtil {
public static final ThreadLocal<Connection> synchronousConnection =
new ThreadLocal<Connection>();
private TransactionUtil() {
}
public static Connection startTransaction() {
Connection connection = synchronousConnection.get();
if (connection == null) {
try {
// 这里替换成你自己的连接地址即可
connection = DriverManager
.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8", "root", "123");
synchronousConnection.set(connection);
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
return connection;
}
public static int execute(String sql, Object... args) {
Connection connection = startTransaction();
try (PreparedStatement preparedStatement = connection.prepareStatement(sql)) {
if (args != null) {
for (int i = 1; i < args.length + 1; i++) {
preparedStatement.setObject(i, args[i - 1]);
}
}
return preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public static void commit() {
try (Connection connection = synchronousConnection.get()) {
connection.commit();
synchronousConnection.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void rollback() {
try (Connection connection = synchronousConnection.get()) {
connection.rollback();
synchronousConnection.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
实际需要事务管理的类
@Component
public class UserService {
public void saveUser() {
TransactionUtil.execute
("INSERT INTO `test`.`user`(`id`, `name`) VALUES (?, ?)", 100, "dmz");
// 测试回滚
// throw new RuntimeException();
}
}
切面:
@Component
@Aspect
public class TxAspect {
@Pointcut("execution(public * com.dmz.mybatis.tx_demo.service..*.*(..))")
private void pointcut() {
}
@Around("pointcut()")
public Object around(JoinPoint joinPoint) throws Throwable {
// 在方法执行前开启事务
TransactionUtil.startTransaction();
// 执行业务逻辑
Object proceed = null;
try {
ProceedingJoinPoint method = (ProceedingJoinPoint) joinPoint;
proceed = method.proceed();
} catch (Throwable throwable) {
// 出现异常进行回滚
TransactionUtil.rollback();
return proceed;
}
// 方法执行完成后提交事务
TransactionUtil.commit();
return proceed;
}
}
用于测试的主函数:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(Config.class);
UserService userService = ac.getBean(UserService.class);
userService.saveUser();
}
}