ThreadLocal+Filter解决事务
前言
ThreadLocal+Filter解决事务,以及如何友好的响应错误页面。
一、ThreadLocal
1、特点
1)可以给当前线程关联一个数据(若是要给一个线程关联多个数据,就应该使用多个ThreadLocal对象),该数据可以是普通变量、对象、数组、集合。(就像是一个线程安全的HashMap,键为当前线程名,值为关联的值)
2)一般定义为static类型对象,保证只有一个且快速获取。
3)在线程销毁时,该对象会由虚拟机自动释放。
2、用处
1)保证每个线程都使用一个数据(变量、对象、数组、集合)。
如Servlet是单例多线程,一个请求一个线程,一次处理业务逻辑一个线程,让这个线程共用一个数据库连接对象,可以来管理事务。
3、如何使用?
package com.xhu.test;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class TestThreadLocal {
public static Map<String, Object> m = new ConcurrentHashMap<>();
public static Map<String, Object> unsafeM = new HashMap<>();
public static ThreadLocal<Object> threadL = new ThreadLocal<>();
public static List<Integer> list = new ArrayList<>();
private static Random random = new Random();
public static class Test implements Runnable {
@Override
public void run() {
//int i = random.nextInt(1000);
//m.put(Thread.currentThread().getName(), i);
//threadL.set(i);
//threadL.get(Thread.currentThread().getName())
list.add(1);
unsafeM.put(Thread.currentThread().getName(), 1);
try {
//System.out.println(Thread.currentThread().getName() + "的数字为:" + i);
//System.out.println(Thread.currentThread().getName() + "的数字为:" + i);
Thread.sleep(0);
//new Service1().service1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test test = new Test();
for (int i = 0; i < 10000; i++) {
new Thread(test).start();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ConcurrentHashMap:" + m.size());
System.out.println("ArrayList:" + list.size());
System.out.println("HashMap:" + unsafeM.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ConcurrentHashMap:" + m.size());//线程安全
System.out.println("ArrayList:" + list.size());//不安全
System.out.println("HashMap:" + unsafeM.size());//不安全
}
}
4、管理事务
JDBC中如何处理事务和改造图,
A.改造事务的源头getConnection
对源头进行管理,就可以管理好事务,所以对一个线程获取连接时进行改造。
private static ThreadLocal<Connection> threadL = new ThreadLocal<>();
private static DataSource ds = null;
static{
Properties pro = new Properties();
InputStream is = new BufferedInputStream(类名.class.getClassLoader().getResourceAsStream("druid.properties"));
try {
pro.load(is);
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取线程中的连接
public static Connection getConnection(){
Connection conn = threadL.get();
if(conn == null){
try {
conn = ds.getConnection();
threadL.set(conn);
conn.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return conn;
}
//统一commit和close
public void closeAndCommit() {
Connection conn = threadL.get();
if(conn != null){
try {
conn.commit();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//统一rollback和close
public void closeAndRollback() {
Connection conn = threadL.get();
if(conn != null){
try {
conn.rollback();
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
B.配合Filter给每个请求加上try-catch
Filter给每个请求加上try-catch来判断是否回滚,所以关于事务中的异常都要上抛,而且不能提前关闭连接。交给Filter,
<filter>
<filter-name>TXFilter</filter-name>
<filter-class>com.xhu.filter.TXFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>TXFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
package com.xhu.filter;
import com.xhu.utils.JDBCUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 统一管理所有事务
*/
public class TXFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest, servletResponse);
JDBCUtils.closeAndCommit();
} catch (Exception e) {
//conn回滚且关闭
JDBCUtils.closeAndRollback();
//此时需要把异常交给Tomcat,Tomcat有对应的处理
throw new RuntimeException();
}
}
}
二、tomcat友好的响应异常
方式1)通过异常上抛给tomcat,让tomcat捕获并做相应处理。方式2)或者上面不是由统一的Filter,可以自己处理。
1、web.xml
<!-- 配置服务器响应异常页面 -->
<error-page>
<error-code>500</error-code>
<location>/pages/error/error500.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/pages/error/notFound404.jsp</location>
</error-page>
2、Filter
package com.xhu.filter;
import com.xhu.utils.JDBCUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 统一管理所有事务
*/
public class TXFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
try {
filterChain.doFilter(servletRequest, servletResponse);
JDBCUtils.closeAndCommit();
} catch (Exception e) {
//conn回滚且关闭
JDBCUtils.closeAndRollback();
//既然能捕获整个请求报的错误,除了上抛给tomcat处理(在web.xml中配置),还可以统一跳到友好的error页面。
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
int status = resp.getStatus();
if (status == 404)
resp.sendRedirect(req.getContextPath() + "/404.jsp");
else if (status == 500)
resp.sendRedirect(req.getContextPath() + "/error.jsp");
}
}
}
总结
1)Filter+ThreadLocal解决事务问题。
2)Tomcat或Filter捕获异常来友好的响应错误页面。
参考文献
[1] JavaWeb 尚硅谷