1、首先通过 SqlSessionFactoryBuilder创建SqlSessionFactory工厂类,再通过SqlSessionFactory工厂类创建SqlSession对象。
- 每个mybatis-config.xml对应一个SqlSessionFactory对象,也就是说如果有多套环境,每个环境创建一个SqlSessionFactory
- SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。
//sqlSessionFactory用工厂模式生产sqlSession
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
//使用mybatis工具类的第一步——获取SqlSessionFactory对象
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
// SqlSession 完全包 含了面向数据库执行 SQL 命令所需的所有方法。
// 你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
public static SqlSession getSqlSession(){
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
}
2、SqlSession接口
//SqlSession接口源码如下所示: package org.apache.ibatis.session; import java.io.Closeable; import java.sql.Connection; import java.util.List; import java.util.Map; import org.apache.ibatis.executor.BatchResult; public interface SqlSession extends Closeable { <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); <E> List<E> selectList(String statement); <E> List<E> selectList(String statement, Object parameter); <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); <K, V> Map<K, V> selectMap(String statement, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); void select(String statement, Object parameter, ResultHandler handler); void select(String statement, ResultHandler handler); void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); int insert(String statement); int insert(String statement, Object parameter); int update(String statement); int update(String statement, Object parameter); int delete(String statement); int delete(String statement, Object parameter); void commit(); void commit(boolean force); void rollback(); void rollback(boolean force); List<BatchResult> flushStatements(); void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> type); Connection getConnection(); }
- 正如其名,Sqlsession对应着一次数据库会话。
- 它是应用程序与持久层之间执行交互操作的一个单线程对象,SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句.
- 由于数据库会话不是永久的,因此Sqlsession的生命周期也不应该是永久的,相反,在你每次访问数据库时都需要创建它(当然并不是说在Sqlsession里只能执行一次sql,你可以执行多次,当一旦关闭了Sqlsession就需要重新创建它)。
- 创建Sqlsession的地方只有一个,那就是SqlsessionFactory的openSession方法
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);
}
- 我们可以看到实际创建SqlSession的地方是openSessionFromDataSource,如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevellevel, boolean autoCommit) {
Connection connection = null;
try {
finalEnvironment environment = configuration.getEnvironment();
final DataSourcedataSource = getDataSourceFromEnvironment(environment);
TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment);
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
connection = wrapConnection(connection);
Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
Executor executor = configuration.newExecutor(tx, execType);
return newDefaultSqlSession(configuration, executor, autoCommit);
} catch (Exceptione) {
close Connection(connection);
throwExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看出,创建sqlsession经过了以下几个主要步骤:
1) 从配置中获取Environment;
2) 从Environment中取得DataSource;
3) 从Environment中取得TransactionFactory;
4) 从DataSource里获取数据库连接对象Connection;
5) 在取得的数据库连接上创建事务对象Transaction;
6) 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
7) 创建sqlsession对象。
3、Executor接口:创建Sqlsession时会创建Executor。Sqlsession对数据库的操作都是通过Executor来完成的,对sqlsession方法的访问最终都会落到executor的相应方法上去。
public interface Executor { ResultHandler NO_RESULT_HANDLER = null; int update(MappedStatement ms, Object parameter) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; List<BatchResult> flushStatements() throws SQLException; void commit(booleanrequired) throws SQLException; void rollback(booleanrequired) throws SQLException; CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); boolean isCached(MappedStatement ms, CacheKey key); void clearLocalCache(); void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType); Transaction getTransaction(); void close(booleanforceRollback); boolean isClosed(); void setExecutorWrapper(Executor executor); }
- Executor接口实现类 :Mybatis默认的Executor是SimpleExecutor
Executor主要完成以下几项内容:
- 处理缓存,包括一级缓存和二级缓存
- 获取数据库连接
- 创建Statement或者PrepareStatement对象
- 访问数据库执行SQL语句
- 处理数据库返回结果
下面就不得不提到mybatis的一级缓存和二级缓存
Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。二级缓存默认关闭。
一级缓存是指 SqlSession 级别的缓存,当在同一个 SqlSession 中进行相同的 SQL 语句查询时,第二次以后的查询不会从数据库查询,而是直接从缓存中获取,一级缓存最多缓存 1024 条 SQL。
二级缓存是指可以跨 SqlSession 的缓存。是 mapper 级别的缓存,对于 mapper 级别的缓存不同的sqlsession 是可以共享的。
- 一级缓存的作用域是一个sqlsession内;
- 二级缓存作用域是针对mapper进行缓存;
一级缓存
public class CacheTest extends BaseMapperTest { @Test public void testL1Cache(){ //获取SqlSession SqlSession sqlSession = getSqlSession(); SysUser user1 = null; try { //获取UserMapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); //调用selectById方法 user1 = userMapper.selectById(1L); //对当前获取的对象重新赋值 user1.setUserName("New Name"); //再次查询获取id相同的用户 SysUser user2 = userMapper.selectById(1L); //虽然没有更新数据库,但是user1和user2的名字相同 Assert.assertEquals("New Name",user2.getUserName()); //无论如何user1和user2是同一个实例 Assert.assertEquals(user1,user2); }finally { sqlSession.close(); } System.out.println("开启新的SqlSession"); //开启一个新的sqlsession sqlSession = getSqlSession(); try { //获取UserMapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); SysUser user2 = userMapper.selectById(1L); Assert.assertNotEquals("New Name",user2.getUserName()); //这里的user2和前一个session查询的结果是两个不同的实例 Assert.assertNotEquals(user1,user2); //执行删除操作 userMapper.deleteById(2L); //获取user3 SysUser user3 = userMapper.selectById(1L); //这里的user2和user3是两个不同的实例 Assert.assertNotEquals(user2,user3); }finally { sqlSession.close(); } } }
运行结果如下,在第一个try语句块中,我们写了两次查询数据库的语句,但是打印出的sql只有一条,证明第二条没有查询数据库,而是查询了缓存,并且user1和user2是同一个实例,先从数据库中获取user1的值,user1重新对userName赋值,第二次查询user2发现user2的userName是user1修改后的值。
Mybatis一级缓存存在于SqlSession的生命周期中,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果放到一个Map中,如果同一个SqlSession中执行的方法和参数完全一致,那么通过算法生成相同的键值,当Map缓存对象中已经 存在该键值时,则会返回缓存中的对象.
第二个try语句块中我们重新获取了一个新的sqlsession,查询结果显示,user2和第一个语句块中的user1没有任何关系,当我们执行删除操作后,用同一个sqlsession执行相同的查询,结果赋值给user3,结果表示user2和user3是不同的实例,原因是因为所有的insert,update,delete操作都会清空一级缓存。不同的sqlsession
@Test public void differSqlSession() { SqlSession sqlSession = null; SqlSession sqlSession2 = null; try { sqlSession = sqlSessionFactory.openSession(); StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); // 执行第一次查询 List<Student> students = studentMapper.selectAll(); for (int i = 0; i < students.size(); i++) { System.out.println(students.get(i)); } System.out.println("=============开始不同 Sqlsession 的第二次查询============"); // 从新创建一个 sqlSession2 进行第二次查询 sqlSession2 = sqlSessionFactory.openSession(); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); List<Student> stus = studentMapper2.selectAll(); // 不相等 Assert.assertNotEquals(students, stus); for (int i = 0; i < stus.size(); i++) { System.out.println("stus:" + stus.get(i)); } } catch (Exception e) { e.printStackTrace(); } finally { if (sqlSession != null) { sqlSession.close(); } if (sqlSession2 != null) { sqlSession2.close(); } } }
二级缓存
在Mybatis的全局配置settings中有一个参数cacheEnabled,这个参数是二级缓存的全局开关.默认为true,所以可以不配置,如果将其设置为false,则在后边所有缓存的配置都不起作用了。
mybatis中的二级缓存是mapper级别的缓存
值得注意的是,不同的mapper都有一个二级缓存,也就是说,不同的mapper之间的二级缓存是互不影响的。为了更加清楚的描述二级缓存,先来看一个示意图:
- sqlSession1去查询用户id为1的用户信息,查询到用户信息会将查询数据存储到该UserMapper的二级缓存中。
- 如果SqlSession3去执行相同 mapper下sql,执行commit提交,则会清空该UserMapper下二级缓存区域的数据。
- sqlSession2去查询用户id为1的用户信息,去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
CachingExecutor
CachingExecutor用于处理二级缓存,如果缓存中不存在要查询的数据,那么将查询请求委托给其他的Executor。如果是执行SQL的增删改,那么CachingExecutor将清空二级缓存。
BaseExecutor
BaseExecutor是除CachingExecutor之外,其他Executor实现类的基类。该类主要处理一级缓存。
当调用该类的查询方法时,先查看一级缓存中是否已经有数据,如果有则直接从缓存获取,如果没有调用子类的查询方法从数据库中获取。
SimpleExecutor
SimpleExecutor继承自BaseExecutor,该类比较简单。
当执行增删改查时,该类获取数据库连接,创建PrepareStatement或者Statement对象,执行SQL语句,最后将数据库返回结果转化为设定的对象。下面以doQuery方法为例://父类执行查询时调用该方法 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //创建StatementHandler对象 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //获得数据库连接,创建Statement或者PrepareStatement stmt = prepareStatement(handler, ms.getStatementLog()); //执行SQL语句,将数据库返回结果转化为设定的对象,比如List,Map或者是POJO return handler.<E>query(stmt, resultHandler); } finally { //关闭Statement对象 closeStatement(stmt); } }