事务的管理与本地线程

引入

image-20220413090242922

以下的注册界面,在注册过程中不应该出现function1成功,而function2不成功的现象,而事务的完成应该以业务为单位进行操作。

  1. 获取链接,取消自动提交conn.setAutoCommit(false)
  2. 执行相应的操作
  3. 提交事务 conn.commit()
  4. 回滚事务 conn.rollback

所以说我们应该在service上开启事务,进行管理,而为了更好的进行判断,我们将此代码前置到filter中

1
2
3
4
5
6
7
try{
autoCommit(false);
放行();
commit();
}catch(){
rollback;
}

但是需要将function1于function2处于同一个链接中

首先我们需要设置过滤器:

1
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
@WebFilter("*.do")
public class OpenSessionInViewFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try{
System.out.println("开始事务");
TransactionManager.beginTrans();
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("提交事务");
TransactionManager.commit();
}catch (Exception e){
e.printStackTrace();
System.out.println("回滚事务");
try {
TransactionManager.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
try {
ConnUtil.closeConn();
} catch (SQLException e) {
e.printStackTrace();
}

}

@Override
public void destroy() {
Filter.super.destroy();
}
}

而在这里负责事务的发送等操作,使过滤器进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TransactionManager {

private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

//开启事务
public static void beginTrans() throws SQLException {
ConnUtil.getConnection().setAutoCommit(false);
}

//提交事务
public static void commit() throws SQLException {
Connection connection= ConnUtil.getConnection();
connection.commit();

}

//回滚事务
public static void rollback() throws SQLException {
Connection connection= ConnUtil.getConnection();
connection.rollback();
}
}

image-20220413152925212

ThreadLocal

本地线程,可以通过set方法在当前线程上存储数据,get可以获取数据

1
2
3
4
5
6
7
8
9
10
11
12
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取维护自己的容器,一个容器中可能含有多个ThreadLocal
ThreadLocalMap map = getMap(t);
if (map != null) {
//this代表当前threadlocal
map.set(this, value);
} else {
createMap(t, value);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前容器维护的map
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取ThreadLocal对象,通过这个才能了解是哪一个工作纽带
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//获得工具箱
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

而一个简易的ThreadLocal可以理解为下:

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。我们自己就可以提供一个简单的实现版本:

1
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
public class MyThreadLocal
{
private final ConcurrentHashMap<Thread, Object> valueMap = new ConcurrentHashMap<>();
public void set(Object newValue)
{
valueMap.put(Thread.currentThread(), newValue);
}
public Object get()
{
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);
if (o == null && !valueMap.containsKey(currentThread))
{
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove()
{
valueMap.remove(Thread.currentThread());
}
public Object initialValue()
{
return null;
}
}