使用AOP功能和ThreadLocal类实现自定义事务管理
使用AOP功能和ThreadLocal类实现自定义事务管理
首先,需要理解ThreadLocal类的作用。ThreadLocal是为了在同一个线程中共享数据,具体原理可以参考源代码,如下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}
设置值实际上就是通过map存放的,与map不同的是固定将当前线程作为key值。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
获取值时通过当前线程去获取值,所以如果在同一个线程中,前面存进去的值,后面是可以取出来用的,以达到线程共享数据的目的。
接下来的例子中,主要是把一个线程中的连接共享,以达到一个线程共享一个连接,因为只有这样,才能够做到事务操作,因为同一个事务必须要求在同一个连接中,才能保证数据安全。具体代码如下:
@Component
public class DBManager {
/**
* 使用map的方式存储数据,key使用当前线程,所以能够保证一个线程共享数据,此处用来在一个线程中共享一个数据库连接
*/
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
@Value("${jdbc.url}") // 使用读取properties配置文件中的数据加载url
private String url;
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.user}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建一个新的连接
* @return
* @throws Exception
*/
private Connection createConnection() throws Exception {
Class.forName(driver);
return DriverManager.getConnection(url, username, password);
}
public Connection getConnection() throws Exception {
// 在ThreadLocal集合中获得以当前线程为key的连接对象
Connection connection = threadLocal.get();
// 如果获取到了连接则返回连接
if (connection != null && !connection.isClosed()){
return connection;
}else {
// 如果连接不存在或者已经关闭则创建新的连接并把连接存到ThreadLocal集合中
connection = createConnection();
threadLocal.set(connection);
return connection;
}
}
public void closeConnection(){
// 在ThreadLocal集合中获得以当前线程为key的连接对象
Connection connection = threadLocal.get();
try {
// 如果获取到了连接则关闭连接,并且将集合中设置为null
if (connection != null && !connection.isClosed()){
connection.close();
threadLocal.set(null);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
在service层中,由于所有的方法都需要处理业务之外的内容,比如连接开启和关闭,事务的提交回滚等,这些应该属于AOP中切面的内容,可以提取出来解决,切面代码如下:(通过设置service层的around通知完成)
@Component
public class MyTransaction {
@Resource
private DBManager dbManager;
// 使用aop的around的方式处理事务
public Object doTransacition(ProceedingJoinPoint pjp){
Connection connection = null;
Object result = null;
// 得到实际调用的service中方法的名称
String methodName = pjp.getSignature().getName();
try {
connection = dbManager.getConnection();
// 判断方法的名称是否需要事务操作(增、删、改需要事务操作)
if (methodName.startsWith("update") || methodName.startsWith("save") ||
methodName.startsWith("delete")){
// 设置事务关闭自动提交
connection.setAutoCommit(false);
// 实际业务方法执行
result = pjp.proceed();
// 提交事务
connection.commit();
}else {
result = pjp.proceed();
}
}catch (Throwable e){
e.printStackTrace();
if (connection != null){
// 判断方法的名称是否需要事务的回滚操作操作(增、删、改需要事务操作)
if (methodName.startsWith("update") || methodName.startsWith("save") ||
methodName.startsWith("delete")){
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
}
}finally {
// 关闭连接
dbManager.closeConnection();
}
return result;
}
}