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