MyBatis原理系列(一)-手把手帶你閱讀MyBatis源碼
MyBatis原理系列(二)-手把手帶你了解MyBatis的啟動流程
MyBatis原理系列(三)-手把手帶你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系
MyBatis原理系列(四)-手把手帶你了解MyBatis的Executor執(zhí)行器
MyBatis原理系列(五)-手把手帶你了解Statement蝶押、StatementHandler碎浇、MappedStatement間的關(guān)系
MyBatis原理系列(六)-手把手帶你了解BoundSql的創(chuàng)建過程
MyBatis原理系列(七)-手把手帶你了解如何自定義插件
MyBatis原理系列(八)-手把手帶你了解一級緩存和二級緩存
MyBatis原理系列(九)-手把手帶你了解MyBatis事務(wù)管理機(jī)制
在上篇文章中察迟,我們講解了MyBatis的啟動流程骤宣,以及啟動過程中涉及到的組件,在本篇文中碳柱,我們繼續(xù)探索SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系读跷。SqlSession作為MyBatis的核心組件,可以說MyBatis的所有操作都是圍繞SqlSession來展開的末誓。對SqlSession理解透徹,才能全面掌握MyBatis书蚪。
1. SqlSession初識
SqlSession在一開始就介紹過是高級接口,類似于JDBC操作的connection對象迅栅,它包裝了數(shù)據(jù)庫連接殊校,通過這個接口我們可以實現(xiàn)增刪改查,提交/回滾事物读存,關(guān)閉連接为流,獲取代理類等操作。SqlSession是個接口让簿,其默認(rèn)實現(xiàn)是DefaultSqlSession敬察。SqlSession是線程不安全的,每個線程都會有自己唯一的SqlSession尔当,不同線程間調(diào)用同一個SqlSession會出現(xiàn)問題莲祸,因此在使用完后需要close掉蹂安。
2. SqlSession的創(chuàng)建
SqlSessionFactoryBuilder的build()方法使用建造者模式創(chuàng)建了SqlSessionFactory接口對象,SqlSessionFactory接口的默認(rèn)實現(xiàn)是DefaultSqlSessionFactory锐帜。SqlSessionFactory使用實例工廠模式來創(chuàng)建SqlSession對象田盈。SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的關(guān)系如下(圖畫得有點丑...):
DefaultSqlSessionFactory中openSession是有兩種方法一種是openSessionFromDataSource,另一種是openSessionFromConnection缴阎。這兩種是什么區(qū)別呢允瞧?從字面意義上將,一種是從數(shù)據(jù)源中獲取SqlSession對象蛮拔,一種是由已有連接獲取SqlSession述暂。SqlSession實際是對數(shù)據(jù)庫連接的一層包裝,數(shù)據(jù)庫連接是個珍貴的資源建炫,如果頻繁的創(chuàng)建銷毀將會影響吞吐量畦韭,因此使用數(shù)據(jù)庫連接池化技術(shù)就可以復(fù)用數(shù)據(jù)庫連接了。因此openSessionFromDataSource會從數(shù)據(jù)庫連接池中獲取一個連接踱卵,然后包裝成一個SqlSession對像廊驼。openSessionFromConnection則是直接包裝已有的連接并返回SqlSession對像。
openSessionFromDataSource 主要經(jīng)歷了以下幾步:
- 從獲取configuration中獲取Environment對象惋砂,Environment包含了數(shù)據(jù)庫配置
- 從Environment獲取DataSource數(shù)據(jù)源
- 從DataSource數(shù)據(jù)源中獲取Connection連接對象
- 從DataSource數(shù)據(jù)源中獲取TransactionFactory事物工廠
- 從TransactionFactory中創(chuàng)建事物Transaction對象
- 創(chuàng)建Executor對象
- 包裝configuration和Executor對象成DefaultSqlSession對象
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
3. SqlSession的使用
SqlSession 獲取成功后妒挎,我們就可以使用其中的方法了,比如直接使用SqlSession發(fā)送sql語句西饵,或者通過mapper映射文件的方式來使用酝掩,在上兩篇文章中我們都是通過mapper映射文件來使用的,接下來就介紹第一種眷柔,直接使用SqlSession發(fā)送sql語句期虾。
public static void main(String[] args){
try {
// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 獲取SqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 獲取SqlSession對象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 執(zhí)行sql
TTestUser user = sqlSession.selectOne("com.example.demo.dao.TTestUserMapper.selectByPrimaryKey", 13L);
log.info("user = [{}]", JSONUtil.toJsonStr(user));
// 5. 關(guān)閉連接
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error("errMsg = [{}]", e.getMessage(), e);
}
}
其中com.example.demo.dao.TTestUserMapper.selectByPrimaryKey指定了TTestUserMapper中selectByPrimaryKey這個方法,在對應(yīng)的mapper/TTestUserMapper.xml我們定義了id一致的sql語句
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_test_user
where id = #{id,jdbcType=BIGINT}
</select>
Mybatis會在一開始加載的時候?qū)⒚總€標(biāo)簽中的sql語句包裝成MappedStatement對象驯嘱,并以類全路徑名+方法名為key镶苞,MappedStatement為value緩存在內(nèi)存中。在執(zhí)行對應(yīng)的方法時鞠评,就會根據(jù)這個唯一路徑找到TTestUserMapper.xml這條sql語句并且執(zhí)行返回結(jié)果茂蚓。
4. SqlSession的執(zhí)行原理
4. 1 SqlSession的selectOne的執(zhí)行原理
SqlSession的selectOne代碼如下,其實是調(diào)用selectList()方法獲取第一條數(shù)據(jù)的剃幌。其中參數(shù)statement就是statement的id聋涨,parameter就是參數(shù)。
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
RowBounds 對象是分頁對象负乡,主要拼接sql中的start,limit條件牍白。并且可以看到兩個重要步驟:
- 從configuration的成員變量mappedStatements中獲取MappedStatement對象绣张。mappedStatements是Map<String, MappedStatement>類型的緩存結(jié)構(gòu)呻惕,其中key就是mapper接口全類名+方法名,MappedStatement就是對標(biāo)簽中配置的sql一個包裝
- 使用executor成員變量來執(zhí)行查詢并且指定結(jié)果處理器,并且返回結(jié)果笼沥。Executor也是mybatis的一個重要的組件印蓖。sql的執(zhí)行都是由Executor對象來操作的低矮。
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
MappedStatement對象的具體內(nèi)容和Executor對象的類型神汹,我們將在其它文章中詳述。
4. 2 SqlSession的通過mapper對象使用的執(zhí)行原理
在啟動流程那篇文章中仑性,我們大致了解了sqlSession.getMapper返回的其實是個代理類MapperProxy惶楼,然后調(diào)mapper接口的方法其實都是調(diào)用MapperProxy的invoke方法,進(jìn)而調(diào)用MapperMethod的execute方法诊杆。
public static void main(String[] args) {
try {
// 1. 讀取配置
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2. 創(chuàng)建SqlSessionFactory工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3. 獲取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 獲取Mapper
TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
// 5. 執(zhí)行接口方法
TTestUser userInfo = userMapper.selectByPrimaryKey(16L);
System.out.println("userInfo = " + JSONUtil.toJsonStr(userInfo));
// 6. 提交事物
sqlSession.commit();
// 7. 關(guān)閉資源
sqlSession.close();
inputStream.close();
} catch (Exception e){
log.error(e.getMessage(), e);
}
}
MapperMethod的execute方法中使用命令模式進(jìn)行增刪改查操作歼捐,其實也是調(diào)用了sqlSession的增刪改查方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
總結(jié)
在這篇文章中我們詳細(xì)介紹了SqlSession的作用晨汹,創(chuàng)建過程豹储,使用方法,以及執(zhí)行原理等淘这,對SqlSession已經(jīng)有了比較全面的了解剥扣。其中涉及到的Executor對象,MappedStatement對象铝穷,ResultHandler我們將在其它文章中講解钠怯。歡迎在評論區(qū)中討論指正,一起進(jìn)步曙聂。