MyBatis源碼分析:SqlSession
1.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.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
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);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, 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();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
從接口的註釋中可以看出,SqlSession接口主要提供如下功能:
- 執行特定SQL:ibatis遺留的使用方式,可通過update,insert,select,delete等方法,帶上命名空間和SQL的id來執行配置好的SQL;
- 獲取映射器(Mapper):讓映射器通過命名空間和方法名稱找到對應SQL,發送給數據庫執行後返回結果;
- 管理事物:ibatis遺留的使用方式,通過commit,rollback方法提交或回滾事物;
2.實現類
SqlSession接口在mybatis中有2個實現類:分別爲DefaultSqlSession和SqlSessionManager。
UML圖如下:
3.ibatis遺留的使用方式
涉及接口(省略了重載):
selectOne()
selectList()
selectMap()
selectCursor()
select()
insert()
update()
delete()
commit()
rollback()
我們以
/**
* Retrieve a list of mapped objects from the statement key and parameter,
* within the specified row bounds.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param rowBounds Bounds to limit object retrieval
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)
接口爲例進行分析,此接口的註釋中聲明:該接口根據statement key和parameter返回查詢結果映射的列表,長度爲irowBounds;泛型T
爲返回結果的類型;statement參數爲命名空間+SQL id;parameter爲傳遞給statemtent的參數;
我們來看一下這個接口在實現類DefaultSqlSession
中的實現:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.從配置中讀取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//2.調用executor的query方法進行查詢
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
這裏涉及到了兩個Mybatis的組件:MappedStatement和Executor。
selectList
方法中使用了一個wrapCollection
方法,該方法將輸入object參數進行封裝,邏輯如下:
- 首先判斷object的類型,若既不是Collection也不是Array,則直接返回;
- 若object的類型爲Collection,且爲List,則構造一個StrictMap對象,並設置兩個enetity,key分別爲"collection"和"list",val均爲object,並將map返回;
- 若object的類型爲Collection,但不爲List,則構造一個StrictMap對象,並設置一個entity,key爲“collection”,val爲object,,並將map返回;
- 若object的類型爲Array,則構造一個StrictMap對象,並設置一個entity,key爲“array”,value爲object,並將map返回;
wrapCollection
方法:
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
selectList
demo:
package mybatis;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
public class HelloWorldMyBatis {
private static SqlSessionFactory sqlSessionFactory = null;
private SqlSessionFactory getSqlSessionFactory() {
if (null == sqlSessionFactory) {
final String resource = "mybatis_config.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
return new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
throw new IllegalStateException("Cannot build SqlSessionFactory", e);
}
} else {
return sqlSessionFactory;
}
}
public static void main(String[] args) {
final SqlSession sqlSession = new HelloWorldMyBatis().getSqlSessionFactory().openSession();
//mapper的命名空間:mybatis.UserMapper
//SQL id:getUser
User user = sqlSession.selectOne("mybatis.UserMapper.getUser", 1L);
System.out.println(user);
sqlSession.close();
}
}
4.通過映射器的方式
涉及接口:
<T> T getMapper(Class<T> type);
此接口在DefaultSqlSession中的實現如下:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
此方法涉及到Mybatis組件:Configuration
demo:
public class HelloWorldMyBatis {
private static SqlSessionFactory sqlSessionFactory = null;
private SqlSessionFactory getSqlSessionFactory() {
if (null == sqlSessionFactory) {
final String resource = "mybatis_config.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
return new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
throw new IllegalStateException("Cannot build SqlSessionFactory", e);
}
} else {
return sqlSessionFactory;
}
}
public static void main(String[] args) {
final SqlSession sqlSession = new HelloWorldMyBatis().getSqlSessionFactory().openSession();
////mapper的命名空間:mybatis.UserMapper
final UserMapper mapper = sqlSession.getMapper(mybatis.UserMapper.class);
//SQL id:getUser
final User user = mapper.getUser(1L);
System.out.println(user);
sqlSession.close();
}
}
5.兩種方式對比
《深入淺出Mybatis技術原理與實戰》一書強烈建議讀者使用映射器的方式來操作數據庫,原因如下:
- Mapper是一個接口,可以進一步屏蔽SqlSession對象,提高代碼可讀性;
- sqlSession.selectOne方法是功能性代碼,參數晦澀難懂,不包含業務邏輯,不符合面向對象編程規範,而mapper.getUser方法符合面向對象編程規範,也更符合業務邏輯;
- 使用mapper的方式,IDE可以檢查java語法,避免不必要的錯誤;