1. 前言
上一篇講了mapper的注冊與擷取,這一節一起看看MapperMethod類
2. 正文
2.1 案例
@Test
public void test(){
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
PersonDao personDao = sqlSession.getMapper(PersonDao.class);
Person p = new Person();
p.setAddress("廣東省");
p.setAge(12);
p.setEmail("[email protected]");
p.setName("chen");
p.setPhone("15345634565");
personDao.insert(p);
System.out.println(p.toString());
sqlSession.commit();
sqlSession.close();
}
2.2 MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {@1
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);@2
result = rowCountResult(sqlSession.insert(command.getName(), param));@3
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;
}
@1 判斷你要執行的語句類型走不同是case
@2 擷取你對象中的參數,上面案例中就是person的對象值
@3 調動sqlSession的insert方法,然後通過rowCountResult封裝insert傳回的對象,最終傳回result,核心的流程就是這一行,進去看下具體的實作
2.3 常見的接口及實作類
在看具體實作之前,先理下幾個核心的接口和實作類
2.3.1 SqlSession
首先是SqlSession,裡面包含了經常操作資料庫用到的一些方法,比如insert/update/selectOne/select/selectList 等等,預設有2個實作類,預設是DefaultSqlSession
2.3.2 Executor 執行器
BaseExecutor是抽象類,下面有3個要重點關注下:
SimpleExecutor 簡單的執行器,就是執行完Statement會及時關掉,也是mybatis預設的執行器。
ReuseExecutor 複用的執行器,會把用過的Statement 用map存起來,key=sql,value=Statement
BatchExecutor 批量的執行器,為了提高性能,一次性操作多個Statement ,執行完,也會及時關閉。
這三個執行器,可以在mybatis 中配置,一種方式是在mybatis-config.xml 配置,另外一種是在spring xml 中配置sqlSession的時候配置。
還有一個特殊的,就是CachingExecutor ,它是把MappedStatement 緩存起來了。
注意:他裡面持有了一個特殊的對象,
private final Executor delegate;
還記得之前擷取session的時候,建構的執行器吧,就是下面這個代碼
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);@1
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);@2
} else {
executor = new SimpleExecutor(this, transaction);@3
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);@4
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
@1、@2、@3 對象建構好後,隻要是開啟了緩存(預設開啟),則會對executor 加一層包裝,進而變成CachingExecutor,到這裡就豁然開朗了吧,也就是上面案例中CachingExecutor 中的delegate 其實就是SimpleExecutor(預設是簡單的執行器);
2.3.2 StatementHandler
這幾個子類其實就是對應jdbc裡面的幾種Statement
SimpleStatementHandler 簡單SQL的處理;
PreparedStatementHandler 預編譯SQL處理;
CallableStatementHandler JDBC中CallableStatement,執行存儲過程相關的接口
RoutingStatementHandler 是核心,也是負責把上面三個handle串起來的關鍵類,主要負責上面三個handle的建立和調用,是以他也和CachingExecutor類似,持有一個
private final StatementHandler delegate;
2.3.3 KeyGenerator
KeyGenerator的功能是生成資料庫主鍵和将insert 生成的主鍵設定到pojo中
重點關注2個實作類:Jdbc3KeyGenerator、SelectKeyGenerator
Jdbc3KeyGenerator表示自增的,也就是資料庫自增後如果需要知道值,比如mysql 表主鍵自增。
這個是将自增結果回填到對象中是從傳回的Statement中擷取id值。配置方式:在 insert 标簽中配置了
keyProperty="id" useGeneratedKeys="true"
屬性
<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
INSERT INTO person (name, age, phone, email, address)
VALUES(#{name},#{age},#{phone},#{email},#{address})
</insert>
SelectKeyGenerator 是通過insert後,再查詢一次,來擷取id的值,Oracle 需要結合sequence 來設定主鍵,就可以用SelectKeyGenerator方式配置。配置方式:在insert标簽内,配置selectKey标簽
<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
<selectKey resultType="int" keyProperty="id" order="BEFORE">
SELECT LAST_INSERT_ID() AS id
</selectKey>
INSERT INTO person (name, age, phone, email, address)
VALUES(#{name},#{age},#{phone},#{email},#{address})
</insert>
BEFORE 表示在插入之前,先查詢一次擷取到id
2.4 核心流程
2.4.1 繼續回到上面2.2
Object param = method.convertArgsToSqlCommandParam(args);@2
result = rowCountResult(sqlSession.insert(command.getName(), param));@3
進入到insert中
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);@1
return executor.update(ms, wrapCollection(parameter));@2
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@1 從config中擷取mapper的資訊
@2 這裡的executor是CachingExecutor,原因上面解釋過了。進入到CachingExecutor#update
2.4.2 CachingExecutor#update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);@1
return delegate.update(ms, parameterObject);@2
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
@1 剛剛說過的, MappedStatement 是有緩存的,這裡需要進行是否需要clear,isFlushCacheRequired 參數是在建立緩存的時候,就需要設定的。
@2 調用抽象類BaseExecutor的update方法
2.4.3 BaseExecutor#update
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());@1
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();@2
return doUpdate(ms, parameter);@3
}
@1 記錄上下文資訊,用于抛異常的時候,擷取資訊。内部通過ThreadLocal來存儲
@2 清除本地緩存
@3 調用子類的doUpdate方法
2.4.4 SimpleExecutor#doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);@1
stmt = prepareStatement(handler, ms.getStatementLog());@2
return handler.update(stmt);@3
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);@4
return stmt;
}
@1 建構StatementHandler 對象,這裡會根據配置的Statement攔截器,執行所有的攔截後,傳回StatementHandler
@2 建構Statement對象,進入到prepareStatement方法,裡面有@4,這裡是設定參數處理handle
最終的執行方法如下:
DefaultParameterHandler#setParameters
@Override
public void setParameters(PreparedStatement ps) {
//記錄上下文
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//擷取需要填充的參數的個數(就是?的個數)
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//?是輸入參數,進入if
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
//判斷SQL中是否包含此屬性
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
//取值
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);
}
//擷取參數類型
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//給pre 設定值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
進入PreparedStatementHandle update 方法
2.4.5 PreparedStatementHandle#update
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();@1
int rows = ps.getUpdateCount();@2
Object parameterObject = boundSql.getParameterObject();@3
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();@4
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);@5
return rows;
}
@1 執行jdbc execute
@2 傳回受影響的記錄條數
@3 擷取插入的對象,上述案例中的person對象
@4 擷取主鍵生成器,設定person id字段的值為剛剛插入的主鍵
2.4.6 繼續回到
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
接下來是sqlSession.insert 傳回受影響的記錄條數,然後通過rowCountResult 傳回一個通用的result.
處理邏輯如下:
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
根據方法的要求的傳回類型,處理傳回值。
3. 總結
insert 時序圖
- 發起insert 請求,擷取mapper代理對象
- 調用代理對象的invoke
- 執行MapperMethod的execute,根據sql語句類型,執行不同case
- DefaultSqlSession#insert 底層調用的也是DefaultSqlSession#update(可以參考DefaultSqlSession.java 183行),調用執行器的update方法
- 擷取預設開啟了緩存,是以是調用CachingExecutor#update
- 調用抽象類的BaseExecutor#update
- 調用子類的具體實作SimpleExecutor#doUpdate
- 執行後,逐漸傳回受影響的記錄條數
4. 參考
[1] https://blog.csdn.net/Roger_CoderLife/article/details/88835765
[2] https://blog.csdn.net/yangliuhbhd/article/details/80982254