使用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;

}

}