学习mybatis不得不了解SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。这里主要是讲解它们的生命周期以及一般最佳实践。 一般来说对象的生命周期也就是对象创建到销毁的过程,如果在这个过程中,如果实现的代码质量不佳,那么很容易造成程序上的错误或者效率的降低。
1、SqlSessionFactoryBuilder
SqlSessionFactoryBuilder可以被jvm虚拟机所实例化、使用或者销毁。一旦使用SqlSessionFactoryBuilder对象创建SqlSessionFactory后,SqlSessionFactoryBuilder类就不需要存在了,也就是,不需要保持对象的状态,可以随意的由jvm销毁。因此SqlSessionFactoryBuilder对象的最佳使用范围是方法内部。
2、SqlSessionFactory
SqlSessionFactory对象是由SqlSessionFactoryBuilder创建。一旦创建SqlSessionFactory类的实例,该实例应该在应用程序执行期间都存在,根本不需要每一次操作数据库时都创建,所以其最佳实践方式就是单例模式,或者使用Spring框架来实现单例模式对SqlSessionFactory对象进行有效管理(后续会讲解)。
3、SqlSession
SqlSession对象由SqlSessionFactory类创建,需要注意的是,每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享,是线程不安全的,其实我们去扒看SqlSession接口定义,可以发现,里面定义了数据的增删改查以及事务处理以及实例对象的关闭。那么很显然在操作该对象的时候必须要保持其线程安全,不然在并发情况下肯定会乱套。或者将SqlSession实例对象存放在一个类的静态字段甚至是实例字段中。
4、简单封装
由1、2、3得知,SqlSessionFactoryBuilder会被jvm自动销毁、SqlSessionFactory不需要每次操作数据库都创建、SqlSession必须是线程安全的,那么针对这3点,以下做了一个简单封装。
SyncSqlSessionFactory类主要是获取SqlSessionFactory单例对象
package com.mybatis.util; import java.io.IOException; import java.io.InputStream; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; public class SyncSqlSessionFactory { private static SqlSessionFactory sqlSessionFactory; private SyncSqlSessionFactory() { } synchronized public static SqlSessionFactory getSqlSessionFactory() { try { if (sqlSessionFactory == null) { String resource = "mybatis-config.xml"; InputStream inputStream = Resources .getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); } } catch (IOException e) { e.printStackTrace(); } return sqlSessionFactory; } }
SafeSqlSessionUtil类保证SqlSession线程安全的前提下封装了事务的回滚提交操作。
package com.mybatis.util; import org.apache.ibatis.session.SqlSession; public class SafeSqlSessionUtil { private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<SqlSession>(); public static SqlSession getSqlSession() { SqlSession sqlSession = threadLocal.get(); return sqlSession == null ? SyncSqlSessionFactory.getSqlSessionFactory() .openSession() : sqlSession; } public static void commit() { if (threadLocal.get() != null) { threadLocal.get().commit(); threadLocal.get().close(); threadLocal.set(null); } } public static void rollback() { if (threadLocal.get() != null) { threadLocal.get().rollback(); threadLocal.get().close(); threadLocal.set(null); } } }
5、使用示例
try { SqlSession sqlSession = SafeSqlSessionUtil.getSqlSession(); //基本操作 sqlSession.delete(""); sqlSession.update(""); sqlSession.select("",null); } catch (Exception e) { SafeSqlSessionUtil.rollback(); }finally{ SafeSqlSessionUtil.commit(); }
6、需要说明的地方
synchronized:如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的类所对应的Class对象。Java中,无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始。
ThreadLocal:线程级别的隔离。它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。多数情况下ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
理解得更加通俗一点就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。