ThreadLocal实现session唯一(转载)

本文介绍如何使用ThreadLocal模式管理Hibernate中的Session,避免多线程环境下频繁创建和销毁Session带来的性能问题。通过ThreadLocal为每个线程提供独立的Session副本,确保线程安全。
在利用Hibernate开发DAO模块时,我们和Session打的交道最多,所以如何合理的管理Session,避免Session的频繁创建和销毁,对于提高系统的性能来说是非常重要的,以往是通过eclipse的插件来自动完成这些代码的,当然效果是不错的,但是总是觉得不爽(没有读懂那些冗长的代码),所以现在打算自己实现Session管理的代码。我们知道Session是由SessionFactory负责创建的,而 SessionFactory的实现是线程安全的,多个并发的线程可以同时访问一个SessionFactory并从中获取Session实例,那么 Session是否是线程安全的呢?很遗憾,答案是否定的。Session中包含了数据库操作相关的状态信息,那么说如果多个线程同时使用一个 Session实例进行CRUD,就很有可能导致数据存取的混乱,你能够想像那些你根本不能预测执行顺序的线程对你的一条记录进行操作的情形吗?
在Session的众多管理方案中,我们今天来认识一种名为ThreadLocal模式的解决方案。
早在Java1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择。ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有一个该变量。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现(为了简单,没有考虑集合的泛型):

public class ThreadLocal {
  private Map values = Collections.synchronizedMap(new HashMap());
  public Object get() {
   Thread currentThread = Thread.currentThread();
    Object result = values.get(currentThread);
   if(result == null&&!values.containsKey(currentThread)) {
    result = initialValue();
    values.put(currentThread, result);
   }
   return result;
  }
  public void set(Object newValue) {
   values.put(Thread.currentThread(), newValue);
  }
  public Object initialValue() {
   return null;
  }
}

那麽具体如何利用ThreadLocal来管理Session呢?Hibernate官方文档手册的示例之中,提供了一个通过ThreadLocal维护Session的好榜样:

public class HibernateUtil {
public static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure()
.buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal session =
new ThreadLocal();
public static Session currentSession() throws HibernateException {
Session s = session.get();
if(s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException {
Session s = session.get();
if(s != null) {
s.close();
}
session.set(null);
}
}

只要借助上面的工具类获取Session实例,我们就可以实现线程范围内的Session共享,从而避免了线程中频繁的创建和销毁Session实例。当然,不要忘记在用完后关闭Session。
写到这里,想再多说一些,也许大多数时候我们的DAO并不会涉及到多线程的情形,比如我们不会将DAO的代码写在Servlet之中,那样不是良好的设计,我自己通常会在service层的代码里访问DAO的方法。但是我还是建议采用以上的工具类来管理Session,毕竟我们不能仅仅考虑今天为自己做什么,还应该考虑明天为自己做什么!
在Web应用中,用户鉴权是确保只有经过授权的用户才能访问特定资源的重要机制。SessionThreadLocal都可用于存储用户信息,为实现用户鉴权提供支持。 ### 使用Session实现用户鉴权 在传统的Web应用中,Session是一种常用的机制,用于在服务器端存储用户的会话信息。当用户登录时,服务器会为其创建一个唯一Session ID,并将其发送给客户端。客户端在后续的请求中会携带该Session ID,服务器通过该ID来识别用户。 ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; // 用户登录时创建Session并存储用户信息 public void login(HttpServletRequest request, String userId) { HttpSession session = request.getSession(true); session.setAttribute("userId", userId); } // 鉴权方法,检查Session中是否存在用户信息 public boolean authenticate(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null && session.getAttribute("userId") != null) { return true; } return false; } ``` ### 使用ThreadLocal实现用户鉴权 在分布式架构中,为了避免每次获取用户信息都查询Redis带来的性能损耗,可使用ThreadLocal来存储当前线程的用户信息。当用户访问需要授权的接口时,可在拦截器中将用户的Token存入ThreadLocal,之后在本次访问中任何需要用户信息的地方都可直接从ThreadLocal中获取数据。 ```java import java.util.HashMap; import java.util.Map; public class AuthToken { private static final ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<>(); // 使用该方法Set用户token public static void map(Map<String, String> map) { threadLocal.set(map); } // 将用户信息存入map public static void setToken(String userId, String token) { Map<String, String> map = new HashMap<>(); map.put(userId, token); map(map); } // 根据键值获取对应的信息 public static String getToken(String userId) { Map<String, String> map = threadLocal.get(); if (map != null) { return map.get(userId); } return null; } // 用完清空ThreadLocal public static void clear() { threadLocal.remove(); } } ``` 在拦截器中使用示例: ```java import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userId = request.getParameter("userId"); String token = "your_token_here"; // 假设从请求中获取或生成Token AuthToken.setToken(userId, token); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { AuthToken.clear(); } } ``` ### 注意事项 虽然ThreadLocal在Web应用中可用于存储当前线程的用户信息等数据,但它并不能解决session共享问题。因为ThreadLocal中的数据是线程隔离的,每个线程都有自己的数据副本,这些数据并不会在服务器之间共享。因此,即使使用ThreadLocal来存储用户信息,如果服务器之间没有共享session信息,那么一个用户在不同服务器上的请求仍然可能会被视为不同的会话 [^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值