Mybatis 整體流程詳解、部分原始碼解讀以及運用到了哪些設計模式
MyBatis主要的類
- Configuration MyBatis所有的配置資訊都維持在Configuration物件之中。
- SqlSession 作為MyBatis工作的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
- Executor MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
- StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合。
- ParameterHandler 負責對使用者傳遞的引數轉換成JDBC Statement 所需要的引數,
- ResultSetHandler 負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合;
- TypeHandler 負責java資料型別和jdbc資料型別之間的對映和轉換
- MappedStatement MappedStatement維護了一條<select|update|delete|insert>節點的封裝,
- SqlSource 負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
- BoundSql 表示動態生成的SQL語句以及相應的引數資訊
設計模式的應用
-
Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
-
工廠模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;
-
單例模式,例如ErrorContext和LogFactory;
-
代理模式,Mybatis實現的核心,比如MapperProxy、ConnectionLogger,用的jdk的動態代理;還有executor.loader包使用了cglib或者javassist達到延遲載入的效果;
-
組合模式,例如SqlNode和各個子類ChooseSqlNode等;
-
模板方法模式,例如BaseExecutor和SimpleExecutor,還有BaseTypeHandler和所有的子類例如IntegerTypeHandler;
-
介面卡模式,例如Log的Mybatis介面和它對jdbc、log4j等各種日誌框架的適配實現;
-
裝飾者模式,例如Cache包中的cache.decorators子包中等各個裝飾者的實現;
-
迭代器模式,例如迭代器模式PropertyTokenizer;
原始碼解讀
public class TestMain {
public static void main(String[] args) throws IOException {
String resouce = "conf.xml";
InputStream is = Resources.getResourceAsStream(resouce);
// 構建sqlSession工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 獲取sqlSession
SqlSession session = sqlSessionFactory.openSession();
User user;
try {
/**
* 第一種方式: 直接執行已對映的 SQL 語句
*/
String statement = "com.hht.dao.UserDao.getById";
user = session.selectOne(statement, 1);
System.out.println(user);
}
finally {
session.close();
}
/**
* 第二種方式: 執行更清晰和型別安全的程式碼
*/
UserDao userDao = session.getMapper(UserDao.class);
user = userDao.getById(1);
System.out.println(user);
}
}
selectOne 原始碼,返回結果超過1報錯
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>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;
}
}
selectOne()會呼叫selectList()
// DefaultSqlSession類
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// CURD操作是交給Excetor去處理的
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();
}
}
executor.query 解讀,使用CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
getBoundSql 封裝sql
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
封裝快取key
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
呼叫下面的方法,這裡根據快取key先從快取獲取,條件是useCache為true,屬於二級快取,預設關閉
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
接下來呼叫BaseExecutor.java 這裡 localCache.getObject(key) 屬於一級快取,預設開啟
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
關於快取:
- 當為select語句時:
flushCache預設為false,表示任何時候語句被呼叫,都不會去清空本地快取和二級快取。
useCache預設為true,表示會將本條語句的結果進行二級快取。
- 當為insert、update、delete語句時:
flushCache預設為true,表示任何時候語句被呼叫,都會導致本地快取和二