User user1 = userDao1.findById(41);
一、動態代理:執行代理對象的方法時攔截,進行方法增強。
/**
* 作用:執行被代理對象的任何接口方法都會經過該方法
* @param proxy : 代理對象的引用
* @param method : 當前執行的方法
* @param args : 當前執行方法所需的參數
* @return : 和被代理對象有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判斷它是否爲類
if (Object.class.equals(method.getDeclaringClass())) {
//如果是的話,直接調用該方法並返回
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
//判斷該方法是不是default方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//對msqlcommand和method進行封裝,並以method:mapperMethod的形式加入methodCache
final MapperMethod mapperMethod = cachedMapperMethod(method);
//返回mapperMethod的execute的返回結果
return mapperMethod.execute(sqlSession, args);
}
可以看看這個MapperMethod具體是個啥玩意兒:
//緩存思想的體現
private MapperMethod cachedMapperMethod(Method method) {
//從methodCache這個Map中取method對應的mapperMethod
MapperMethod mapperMethod = methodCache.get(method);
//如果裏面沒有,就創建一個
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
//以method:mapperMethod的形式加入methodCache
methodCache.put(method, mapperMethod);
}
//如果有就直接返回
return mapperMethod;
}
MapperMethod的構造器,sqlCommand和methodSignature是他的兩個靜態內部類:
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
二、接着執行MapperMethod對象的execute方法,其實源碼還是通俗易懂的,無非就是按照不同的sql語句的類別進行不同的數據結果的封裝,值得注意的是,insert,update和delete其實底層都是調用了update方法,但爲了語義清晰,所以區分類別。
之前command封裝了sql語句的類別,我們這是SELECT
對吧,
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 {
//將Args轉換爲SqlCommand參數,簡單理解就是獲取了參數41,這裏就不深入了
Object param = method.convertArgsToSqlCommandParam(args);
//調用selectOne方法,這部分可以發現,無論是使用代理dao還是定義sqlsession實現類,本質上都調用了這些方法,因爲這裏的command。getName就是具體定義的sql的namespace.id
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
三、當然本例以findById爲例,這裏調用的是SelectOne方法,接收com.smday.dao.IUserDao.findById
和41
。
@Override
public <T> T selectOne(String statement, Object parameter) {
//根據參數select List
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 {
//個數爲0返回null
return null;
}
}
四、調用selectList的方法,實現如下:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//獲取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//wrapCollection方法是對集合類型或者數組類型的參數做特殊處理
//通過執行器調用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();
}
}
五、獲取MappedStatement對象,該對象代表一個增刪改查標籤的詳細信息。
六、默認執行CachingExecutor.query(ms,xxx,x)方法,獲取boundsql,該對象包含sql的具體信息,創建緩存key。
七、先去二級緩存中查詢數據,如果二級緩存中沒有,則去一級緩存(localCache)中查詢,接着數據庫(queryFromDatabase)一條龍服務,這部分就不贅述了。最終調用的是Executor的doQuery方法,list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
。
八、創建StatementHandler對象,默認爲PreparedStatementHandler,用以操作statement執行操作。
ps:StatementHandler定義了一些主要的方法:預編譯相關prepare、查詢query、設置參數parameterize等等。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//從mappedStatement中獲取配置信息對象
Configuration configuration = ms.getConfiguration();
//創建StatementHandler對象,處理sql語句的對象,默認爲PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//創建prepareStatement對象
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler並不是真實的服務對象,將會通過適配器模式找到對應的Statementhandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//攔截鏈對方法進行攔截
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
Executor和Statement分爲三種:Simple、Prepared、Callable。
SqlSession四大對象在創建的時候都會被攔截器進行攔截,我們之後再做學習。
九、在創建StatementHandler的時候,我們會發現,它還初始化創建了另外兩個重要的對象:
//用於參數處理
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//用於封裝結果集
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
十、在創建prepareStatement對象的時候,其實還通過parameterHandler的prepare()對statement進行了參數的預編譯:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//預編譯(基礎配置)
stmt = handler.prepare(connection, transaction.getTimeout());
//設置參數
handler.parameterize(stmt);
return stmt;
}
//statementhandler的方法
public Statement prepare(Connection connection, Integer transactionTimeout)
Statement statement = null;
//預編譯
statement = instantiateStatement(connection);
//設置超時
setStatementTimeout(statement, transactionTimeout);
//設置獲取最大行數
setFetchSize(statement);
return statement;
還通過handler.parameterize(stmt);
對參數進行設置,最終通過parameterHandler的setParameters的方法實現了該操作,其中還創建TypeHandler對象完成數據庫類型和javaBean類型的映射。
@Override
public void setParameters(PreparedStatement ps) {
//。。。省略對value值的操作
//創建TypeHandler對象完成數據庫類型和javaBean類型的映射
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//設置參數
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
十一、獲取了ps參數之後,就可以執行statementHandler的query方法進行查詢了
//PreparedStatementHandler.java
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//轉爲PreparedStatement對象
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
//利用結果集處理對象對結果集進行處理:封裝並返回。
return resultSetHandler.<E> handleResultSets(ps);
}
總結:
反射技術運用廣泛,基於反射的動態代理模式使我們操作的不再是真實的服務,而是代理對象,正是基於動態代理,mybatis可以在真實對象的基礎上,提供額外的服務,我們也可以利用這一特性去自定義一些類,滿足我們的需求。
-
通過動態代理調用代理對象的方法。
-
通過sqlSession執行sql操作的方法:insert|delete|select|update
-
利用Executor對象對其他三大對象進行調度。
-
PreparedStatementHandler對sql進行預編譯,並進行了基礎配置,接着設置參數,並執行sql語句。
-
ParameterHandler負責對參數進行設置,其中TypeHandler負責數據庫類型和javabean類型的映射。
-
最後查詢結果由ResultHandler封裝。