ThreadLocal在Mybatis工具类中的作用(为什么要使用ThreadLocal)

以下思路来自下面几个帖子,总结仅为个人理解,欢迎指正错误!
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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值