以下思路来自下面几个帖子,总结仅为个人理解,欢迎指正错误!
https://bbs.csdn.net/topics/320209150
https://zhuanlan.zhihu.com/p/82737256
https://www.cnblogs.com/dolphin0520/p/3920407.html
在web应用中,客户端发起请求到servlet,servlet创建一个service对象,service在去调用dao进行数据查询,这里使用如下mybatis工具类返回一个sqlsession对象:进行数据操作
工具类
public class MySessionUtils {
private static SqlSessionFactory sessionFactory;
//static 静态代码,在类加载的时候执行一次,且只执行一次
static{
//1 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2 创建SqlSessionFactory对象
//3 加载SqlMapConfig.xml配置文件
InputStream inputStream = MySessionUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
sessionFactory = sqlSessionFactoryBuilder.build(inputStream);//加载核心配置文件 参1 输入流
}
public static SqlSession getSession() {
//4 创建SqlSession对象
SqlSession sqlSession = sessionFactory.openSession();
return sqlSession;
}
}
在service中进行数据插入操作
public class InsertOrderService(){
//获得session对象
public void createOrder(){
SqlSession sqlsessio = MySessionUtils.getSession();
OrderDao mapper = sqlsessio.getMapper(OrderDao.class);
mapper.insertUser(new Order(9,1,1000111,new Date(),"无"));
//调用getMapper
sqlsessio.commit();
sqlsessio.close();
//查询订单
//关闭连接
}
}
问题:在service中出现了dao层的操作对象,造成了耦合。如果后期需要换掉mybatis,那么service中就会出现一大堆报错,有关sqlsession对象的操作会全部报错,如下图。dao层的操作对象代码本不应该出现在service中,所以我们希望进一步将sqlsession对象封装到工具内内部。
改造后的工具类:
public class MySessionUtils {
private static SqlSessionFactory sessionFactory;
private static SqlSession sqlSession;
//static 静态代码,在类加载的时候执行一次,且只执行一次
static{
//1 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2 创建SqlSessionFactory对象
//3 加载SqlMapConfig.xml配置文件
InputStream inputStream = MySessionUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
sessionFactory = sqlSessionFactoryBuilder.build(inputStream);//加载核心配置文件 参1 输入流
}
public static SqlSession getSession() {
//4 创建SqlSession对象
sqlSession = sessionFactory.openSession();
return sqlSession;
}
public static void closes(){
sqlSession.close();
}
}
以上工具类在单线程的时候没有问题,但是在多线程情况下就出现了新的问题,我们将sqlsession做为静态变量,就会出现多个线程得到的同一个sqlsession,就会出现线程安全问题。
如果将sqlsession做为实例变量,这样就不会造成线程安全问题了?
答案:是的,但是每一次同一个线程获取到的sqlsession也不一样了,这样对于事务处理来说就不方便了。且在高并发的时候,由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。
那如果将sqlsession作为参数在方法中传递呢?
同样可以实现一个线程前后使用的是同一个sqlsession,但是这又出现了最开始解决的耦合问题,且在方法中传递sqlsession实在不方便,如果是一些封装好的类,我们还不能使用参数传递sqlsession。
所以使用ThreadLocal来保存一个线程的sqlsession,在同一个线程下如果要使用就直接取出使用,确保了不同线程不会拿到同一个sqlsession而引发的线程安全问题,同时也解决了同一个线程拿到不同的sqlsession而出现的事务问题。
最终改进的工具类:
public class MySessionUtils {
private static SqlSessionFactory sessionFactory;
//static 静态代码,在类加载的时候执行一次,且只执行一次
static{
// 》1 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 》2 创建SqlSessionFactory对象
InputStream inputStream = MySessionUtils.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
sessionFactory = sqlSessionFactoryBuilder.build(inputStream);//加载核心配置文件 参1 输入流
// 》3 加载SqlMapConfig.xml配置文件
}
//A: 定义一个ThreadLocal集合,本质是Map<Thread,Object> map
private static ThreadLocal<SqlSession> map = new ThreadLocal<SqlSession>();
public static SqlSession getSession() {
//查找在local中,是否有对应的SqlSession
SqlSession sqlSession = map.get(); //map.get(Thread.currentThread())
if (sqlSession != null) {
//有就直接返回给调用者使用
return sqlSession;
} else {
//没有就创建一个新的,并且保存在local
sqlSession = sessionFactory.openSession();
//保存
map.set(sqlSession);
return sqlSession;
}
}
public static void commitAndClose() {
//将来进行写操作,之后需要提交,我们定义的方法
SqlSession session = map.get();
if (session != null) {
session.commit();//提交
session.close();//释放
//已经关闭的session不能留在local
//所以要删除
map.remove();
}
}
public static void rollbackAndClose() {
//将来进行写操作,之后需要提交,我们定义的方法
SqlSession session = map.get();
if (session != null) {
session.rollback();//回滚
session.close();//释放
//已经关闭的session不能留在local
//所以要删除
map.remove();
}
}
public static <T> T getMapper(Class clz) {
return (T) getSession().getMapper(clz);
}
}