字節(jié)跳動飛書內(nèi)推!
北京他炊、杭州争剿、武漢、廣州痊末、深圳蚕苇、上海,六大城市等你來投凿叠。
感興趣的朋友可以私我咨詢&內(nèi)推涩笤,也可以通過鏈接直接投遞!
海量HC,極速響應(yīng)辆它,快來和我成為同事吧。
今日頭條履恩、抖音锰茉、Tik Tok也可以內(nèi)推~
點(diǎn)擊進(jìn)入我的博客
MyBatis詳解1.概述
MyBatis詳解2.MyBatis使用入門
MyBatis詳解3.MyBatis配置詳解
MyBatis詳解4.映射器Mapper
MyBatis詳解5.動態(tài)SQL
MyBatis詳解6.MyBatis技術(shù)內(nèi)幕
MyBatis詳解7.插件
MyBatis詳解8.集成Spring
1 構(gòu)建SqlSessionFactory的過程
通過SqlSessionFactoryBuilder構(gòu)建SqlSessionFactory共有兩步:
- 通過XMLConfigBuilder解析MyBatis的配置文件,并讀取到Confinguration對象中切心。
- 使用Confinguration對象去創(chuàng)建SqlSessionFactory飒筑,由于SqlSessionFactory是一個接口,最終構(gòu)建出的其實是DefaultSqlSessionFactory的對象绽昏。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 通過XMLConfigBuilder讀入配置
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
// ......
}
}
public SqlSessionFactory build(Configuration config) {
// 返回的是DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
Configuration的主要作用如下:
- 讀入配置文件协屡,包括基礎(chǔ)配置的XML文件和映射器的XML文件。
- 初始化基礎(chǔ)配置全谤,比如MyBatis的別名等肤晓,一些重要的類對象如,插件认然、映射器补憾、 ObjectFactory和typeHandler對象。
- 提供單例卷员,為后續(xù)創(chuàng)建SessionFactory服務(wù)并提供配置的參數(shù)盈匾。
- 執(zhí)行一些重要的對象方法,初始化配置信息毕骡。
2 映射器的內(nèi)部構(gòu)成
一個映射器是由3個部分組成
- MappedStatement削饵,它保存映射器的一個節(jié)點(diǎn)(select、insert未巫、delete窿撬、update)。包括許多我們配置的SQL橱赠、SQL的id尤仍、緩存信息、resultMap狭姨、parameterType宰啦、resultType、languageDriver等重要配置內(nèi)容饼拍。
- SqlSource赡模,是一個接口,它的主要作用是根據(jù)參數(shù)和其他的規(guī)則組裝SQL师抄,它是提供BoundSql對象的地方漓柑,它是MappedStatement的一個屬性。
- BoundSql,它是建立SQL和參數(shù)的地方辆布。它有3個常用的屬性:SQL瞬矩、parameterObject、parameterMappings锋玲。
BoundSql的主要屬性
- BoundSql會提供3個主要的屬性:parameterObject景用、parameterMappings和sql
- parameterObject為參數(shù)本身,可以傳遞簡單對象惭蹂、POJO伞插、Map或者@Param注解的參數(shù)。
- parameterMappings盾碗,它是一個List媚污,每一個元素都是ParameterMapping的對象。這個對象會描述我們的參數(shù)廷雅,包括屬性耗美、名稱、表達(dá)式榜轿、 javaType幽歼、 jdbcType、typeHandler等重要信息谬盐。通過它可以實現(xiàn)參數(shù)和SQL的結(jié)合甸私,以便PreparedStatement能夠通過它找到parameterObject對象的屬性并設(shè)置參數(shù),使得程序準(zhǔn)確運(yùn)行飞傀。
- sql屬性就是我們書寫在映射器里面的一條SQL皇型,在插件的情況下,我們可以根據(jù)需要進(jìn)行改寫砸烦。
parameterObject的細(xì)節(jié)
- 傳遞簡單對象(如基本數(shù)據(jù)類型)時弃鸦,例如當(dāng)我們傳遞int類型時,MyBatis會把參數(shù)變?yōu)镮nteger對象傳遞幢痘。如果我們傳遞的是POJO或者M(jìn)ap唬格,那么這個parameterObject就是你傳入的POJO或者M(jìn)ap不變。
- 當(dāng)然我們也可以傳遞多個參數(shù)颜说,如果沒有@Param注解购岗,那么MyBatis就會把parameterObject變?yōu)橐粋€Map<String, Object>對象,其鍵值的關(guān)系是按順序來規(guī)劃的门粪,類似于這樣的形式:
{"1":p1; "2":p2, "3":p3,...,"param1":pl, "param2":p2, "param3":p3}
喊积,可以使用#{param1}或者#{1}去引用第1個參數(shù)。 - 如果我們使用@Param注解,那么MyBatis就會把parameterObject會變?yōu)橐粋€Map<String, Object>對象玄妈,鍵為@Param注解的鍵乾吻。
3 SqlSession的運(yùn)行過程
3.1 Mapper的動態(tài)代理
我們自定義的Mapper接口想要發(fā)揮功能髓梅,必須有具體的實現(xiàn)類,在MyBatis中是通過為Mapper每個接口提供一個動態(tài)代理類來實現(xiàn)的绎签。整個過程主要有三個類:MapperProxyFactory枯饿、MapperProxy、MapperMethod诡必。
- MapperProxyFactory就是MapperProxy的工廠類鸭你,主要方法就是包裝了Java動態(tài)代理的Proxy.newProxyInstance()方法。
- MapperProxy就是一個動態(tài)代理類擒权,它實現(xiàn)了InvocationHandler接口。對于代理對象的調(diào)用都會被代理到InvocationHandler#invoke方法上阁谆。
- MapperMethod包含了具體增刪改查方法的實現(xiàn)邏輯碳抄。
public class MapperProxyFactory<T> {
// 這里可以看到是通過Java的動態(tài)代理來實現(xiàn)的,具體代理的方法被放到來MapperProxy中
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
// 實現(xiàn)了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 對代理類的所有方法的執(zhí)行场绿,都會進(jìn)入到invoke方法中
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 此處判斷是否是Object類的方法剖效,如toString()、clone()焰盗,如果是則直接執(zhí)行不進(jìn)行代理
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 如果不是Object類的方法璧尸,則初始化一個MapperMethod并放入緩存中
// 或者從緩存中取出之前的MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 調(diào)用MapperMethod執(zhí)行對應(yīng)
return mapperMethod.execute(sqlSession, args);
}
}
public class MapperMethod {
// MapperMethod采用命令模式運(yùn)行,根據(jù)上下文跳轉(zhuǎn)熬拒,它可能跳轉(zhuǎn)到許多方法中
// 實際上它最后就是通過SqlSession對象去運(yùn)行對象的SQL爷光。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: { //...
}
case UPDATE: { //...
}
case DELETE: { //...
}
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);
}
break;
case FLUSH:
//...
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
}
3.2 SqlSession中的對象
Mapper執(zhí)行的過程是通過Executor、StatementHandler澎粟、ParameterHandler和ResultHandler來完成數(shù)據(jù)庫操作和結(jié)果返回的:
- Executor代表執(zhí)行器蛀序,由它來調(diào)度StatementHandler、ParameterHandler活烙、ResultHandler等來執(zhí)行對應(yīng)的SQL徐裸。
- StatementHandler的作用是使用數(shù)據(jù)庫的Statement(PreparedStatement)執(zhí)行操作,起到承上啟下的作用啸盏。
- ParameterHandler用于SQL對參數(shù)的處理重贺。
- ResultHandler是進(jìn)行最后數(shù)據(jù)集(ResultSet)的封裝返回處理的。
3.3 執(zhí)行器Executor
執(zhí)行器是一個真正執(zhí)行Java和數(shù)據(jù)庫交互的類回懦,一共有三種執(zhí)行器气笙,我們可以在MyBatis的配置文件中設(shè)置defaultExecutorType屬性進(jìn)行選擇。
- SIMPLE(
org.apache.ibatis.executor.SimpleExecutor
)粉怕,簡易執(zhí)行器健民,默認(rèn)執(zhí)行器。 - REUSE(
org.apache.ibatis.executor.ReuseExecutor
)贫贝,是一種執(zhí)行器重用預(yù)處理語句秉犹。 - BATCH(
org.apache.ibatis.executor.BatchExecutor
)蛉谜,執(zhí)行器重用語句和批量更新,它是針對批量專用的執(zhí)行器崇堵。
// Configure類中創(chuàng)建Executor的具體邏輯
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);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 在executor完成創(chuàng)建之后型诚,會通過interceptorChain來添加插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
創(chuàng)建Executor的具體邏輯在Configure類中,可以看到鸳劳,在Executor創(chuàng)建完成之后狰贯,會通過interceptorChain來添加插件,通過代理到方式赏廓,在調(diào)度真實的Executor方法之前執(zhí)行插件代碼來完成功能涵紊。
Executor的具體執(zhí)行邏輯
我們通過SimpleExecutor來看一下Executor的具體執(zhí)行邏輯:
- 根據(jù)Configuration來構(gòu)建StatementHandler
- 然后使用prepareStatement方法,對SQL編譯并對參數(shù)進(jìn)行初始化
- 在prepareStatement方法中幔摸,調(diào)用了StatementHandler的prepared進(jìn)行了預(yù)編譯和基礎(chǔ)設(shè)置摸柄,然后通過StatementHandler的parameterize來設(shè)置參數(shù)并執(zhí)行。
- 包裝好的Statement通過StatementHandler來執(zhí)行既忆,并把結(jié)果傳遞給resultHandler驱负。
public class SimpleExecutor extends BaseExecutor {
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// (1)根據(jù) Configuration來構(gòu)建Statementhandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} 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);
return stmt;
}
}
3.4 數(shù)據(jù)庫會話器StatementHandler
StatementHandler就是專門處理數(shù)據(jù)庫會話的,創(chuàng)建StatementHandler的過程在Configuration中患雇。
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
}
很顯然創(chuàng)建的真實對象是一個RoutingStatementHandler對象跃脊,它實現(xiàn)了接口StatementHandler。從RoutingStatementHandler的構(gòu)造方法來看苛吱,它其實是使用來委派模式來把具體的StatementHandler類型隱藏起來酪术,通過RoutingStatementHandler來統(tǒng)一管理。一共用三種具體的StatementHandler類型:SimpleHandler翠储、PreparedStatementHandler拼缝、CallableStatementHandler。
通過StatementHandler看執(zhí)行細(xì)節(jié)
在Executor的具體執(zhí)行邏輯中彰亥,我們主要關(guān)注StatementHandler的prepared咧七、parameterize兩個方法。
public abstract class BaseStatementHandler implements StatementHandler {
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// instantiateStatement對SQL進(jìn)行了預(yù)編譯
statement = instantiateStatement(connection);
// 設(shè)置超時時間
setStatementTimeout(statement, transactionTimeout);
// 設(shè)置獲取最大的行數(shù)
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
// 調(diào)用parameterize去設(shè)置參數(shù)任斋,可以發(fā)現(xiàn)是通過parameterHandler來具體執(zhí)行的
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
}
public class PreparedStatementHandler extends BaseStatementHandler {
// 具體的查詢就是通過PreparedStatement#execute來執(zhí)行的
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
}
3.5 參數(shù)處理器ParameterHandler
MyBatis是通過ParameterHandler對預(yù)編譯的語句進(jìn)行參數(shù)設(shè)置的继阻。
public interface ParameterHandler {
// 返回參數(shù)對象
Object getParameterObject();
// 設(shè)置預(yù)編譯的SQL語句的參數(shù)
void setParameters(PreparedStatement ps) throws SQLException;
}
public class DefaultParameterHandler implements ParameterHandler {
@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 (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
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 {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
MyBatis為ParameterHandler提供了一個實現(xiàn)類DefaultParameterHandler,具體執(zhí)行過程還是從 parameterObject對象中取參數(shù)然后使用typeHandler進(jìn)行參數(shù)處理废酷,而typeHandler也是在My Batis初始化的時候瘟檩,注冊在Configuration里面的,我們需要的時候可以直接拿來用澈蟆。
3.6 ResultSetHandler
public interface ResultSetHandler {
// 包裝結(jié)果集的
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
// 處理存儲過程輸出參數(shù)的
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
MyBatis為我們提供了一個DefaultResultSetHandler類墨辛,在默認(rèn)的情況下都是通過這個類進(jìn)行處理的。這個類JAVASSIST或者CGLIB作為延遲加載趴俘,然后通過typeHandler和ObjectFactory進(jìn)行組裝結(jié)果再返回睹簇。