MyBatis 二級緩存實現(xiàn)機(jī)制的示例解析
前置準(zhǔn)備
既然要看MyBatis源碼,當(dāng)然是把源碼拉取下來debug一步一步看才方便呢,這里已經(jīng)拉取下來,并準(zhǔn)備好例子了。
@Slf4j
public class Main {
public static void main(String[] args) {
String resource = "org/apache/ibatis/yyqtest/resource/mybatis-config.xml";
InputStream inputStream = null;
try {
// 將XML配置文件構(gòu)建為Configuration配置類
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 通過加載配置文件流構(gòu)建一個SqlSessionFactory DefaultSqlSessionFactory
SqlSessionFactory sqlSessionFactory = null;
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
//Role role = roleMapper.testManyParam(1L, "張三");
//Role role = roleMapper.testAnnotation(1L);
Role role = roleMapper.testDynamicParam(1L, "");
log.info(role.getId() + ":" + role.getRoleName() + ":" + role.getNote());
sqlSession.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
sqlSession.rollback();
e.printStackTrace();
} finally {
sqlSession.close();
}
}
}
從上訴例子來看,我們觀察到首先是解析配置文件,再獲取SqlSession,獲取到Sqlsession之后在進(jìn)行各種的CRUD操作,我們先來看下SqlSession是怎么獲取的。
SqlSession與SqlSessionFactory

?我們根據(jù)時序圖,一步一步解開SqlSession的執(zhí)行流程,首先來是通過SqlSessionFactoryBuilder解析配置文件,再根據(jù)配置文件構(gòu)建并返回一個DefaultSqlSessionFactory,源碼如下:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 2. 創(chuàng)建XMLConfigBuilder對象用來解析XML配置文件,生成Configuration對象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 3. 將XML配置文件內(nèi)的信息解析成Java對象Configuration對象
Configuration config = parser.parse();
// 4. 根據(jù)Configuration對象創(chuàng)建出SqlSessionFactory對象
return build(config);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
拿到SqlSessionFactory之后,就可以根據(jù)這個SqlSessionFactory拿到SqlSession對象,源碼如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 通過事務(wù)工廠從一個數(shù)據(jù)源來產(chǎn)生一個事務(wù)
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 獲取執(zhí)行器,這邊獲得的執(zhí)行器已經(jīng)代理攔截器的功能
final Executor executor = configuration.newExecutor(tx, execType);
// 獲取執(zhí)行器后,再將configuration和executor封裝成DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通過上面兩個步驟拿到SqlSession之后,就可以進(jìn)行CRUD了
- 將xml配置文件解析成流
SqlSessionFactory.build將配置文件解析成Configuration,并返回一個SqlSessionFactory對象- 拿到
SqlSessionFactory對象之后,從Environment中拿到DataSource并產(chǎn)生一個事務(wù) - 根據(jù)事務(wù)
Transaction和執(zhí)行器類型(SIMPLE, REUSE, BATCH,默認(rèn)是SIMPLE)獲取一個執(zhí)行器Executor(–>該對象非常重要,事實上sqlsession的所有操作都是通過它完成的) - 創(chuàng)建sqlsession對象。
MapperProxy
拿到Sqlsession對象之后,就可以拿到我們所寫的xxxMapper,再根據(jù)這個xxxmapper就可以調(diào)用方法,最終進(jìn)行CRUD。我們來關(guān)注下我們這個xxxMapper是一個接口,接口是不能被實例化的,為什么可以直接通過getMapper方式拿到呢?xxxMapper和我們的xxxMapper.xml文件之間又是如何聯(lián)系的?別急,我們接著往下看。
我們先來看下時序圖:

通過這個時序圖可以看到是通過代理的方式來搞定的,當(dāng)執(zhí)行到自己寫的方法里面的時候,其實通過了MapperProxy在進(jìn)行代理。話不多說,我們直接來看下怎么獲取MapperProxy對象的吧: 哦吼↑ ↓
DefaultSqlSession
/**
* 什么都不做,直接去configuration里面去找
* @param type Mapper interface class
* @param <T>
* @return
*/
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
Configuration
// mapperRegistry實質(zhì)上是一個Map,里面注冊了啟動過程中解析的各種Mapper.xml
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
/**
* configuration也什么都不做,去mapperRegistry里面找
* @param type
* @param sqlSession
* @param <T>
* @return
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
這里會發(fā)現(xiàn),這個mapperRegistry是什么東西啊,mapperRegistry實質(zhì)上是一個Map,里面注冊了啟動過程中解析的各種Mapper.xml。我們點進(jìn)去看看
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
mapperRegistry的key是接口的Class類型
mapperRegistry的Value是MapperProxyFactory,用于生成對應(yīng)的MapperProxy(動態(tài)代理類)
?這里具體是怎么加進(jìn)去的可以去了解了解解析配置的篇章,我們接著往下走,看看MapperRegistry的源碼:
MapperRegistry
/**
* MapperRegistry標(biāo)識很無奈,沒人做,只有我來做了
* @param type
* @param sqlSession
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// MapperRegistry表示也要偷懶,偷偷的把粗活交給MapperProxyFactory去做。
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 如果配置文件中沒有配置相關(guān)Mapper,直接拋異常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 我們看到mapper都是接口,因為接口是不能實例化的,這里通過動態(tài)代理實現(xiàn),具體可以看下MapperProxy這個類
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory這里是將接口實例化的過程,我們進(jìn)去看看:
MapperProxyFactory
public class MapperProxyFactory<T> {
/**
* 生成Mapper接口的動態(tài)代理類MapperProxy,MapperProxy實現(xiàn)了InvocationHandler接口
*
* @param mapperProxy
* @return
*/
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 動態(tài)代理,我們寫的一些接口
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
通過這個MapperProxyFactory就可以拿到類MapperProxy代理對象,簡單來說,我們通過這個代理就可以很方便的去使用我們寫的一些dao層的接口了,上例子
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); Role role = roleMapper.testManyParam(1L, "張三"); Role role = roleMapper.testAnnotation(1L); Role role = roleMapper.testDynamicParam(1L, "");
拿到這個對象之后,接下來就是sql語句的執(zhí)行了呢,來,上源碼,真正的主菜來了。
Executor
SqlSession只是一個門面,前面做的一些事情都只是鋪墊,真正的的干事的其實是Excutor,SqlSession對數(shù)據(jù)庫的操作都是通過Executor來完成的。與SqlSession一樣,Executor也是動態(tài)創(chuàng)建的,老樣子先上時序圖:

在看MapperProxy的方法之前,我們先回到獲取Excutor對象的方法configuration.newExecutor(tx, execType)
/**
* Executor分成兩大類,一類是CacheExecutor,另一類是普通Executor。
*
* @param transaction
* @param executorType
* @return
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
// 這句再做一下保護(hù),囧,防止粗心大意的人將defaultExecutorType設(shè)成null?
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 普通Executor又分為三種基本的Executor執(zhí)行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 執(zhí)行update或select,以sql作為key查找Statement對象,存在就使用,不存在就創(chuàng)建,用完后,不關(guān)閉Statement對象,而是放置于Map<String,Statement>內(nèi),供下一次使用。簡言之,就是重復(fù)使用Statement對象
executor = new ReuseExecutor(this, transaction);
} else {
// 每執(zhí)行一次update或select,就開啟一個Statement對象,用完立刻關(guān)閉Statement對象。
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
// CacheExecutor其實是封裝了普通的Executor,和普通的區(qū)別是在查詢前先會查詢緩存中
// 是否存在結(jié)果,如果存在就使用緩存中的結(jié)果,如果不存在還是使用普通的Executor進(jìn)行
// 查詢,再將查詢出來的結(jié)果存入緩存。
executor = new CachingExecutor(executor);
}
// 調(diào)用每個攔截器的方法,這里的話應(yīng)該是為了方便擴(kuò)展,方便開發(fā)者自定義攔截器做一些處理
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
從上訴代碼可以看出,如果不開啟緩存cache的話,創(chuàng)建的Executor只是3中基礎(chǔ)類型之一,BatchExecutor專門用于執(zhí)行批量sql操作,ReuseExecutor會重用statement執(zhí)行sql操作,SimpleExecutor只是簡單執(zhí)行sql沒有什么特別的。
?開啟了緩存(默認(rèn)開啟),就會去創(chuàng)建CachingExecutor,這個緩存執(zhí)行器在查詢數(shù)據(jù)庫前會先查找緩存是否存在結(jié)果,如果存在就使用緩存中的結(jié)果,如果不存在還是使用普通的Executor進(jìn)行查詢,再將查詢出來的結(jié)果存入緩存。我們還看到這里有個加載插件的方法,就說明這里是可以被插件攔截的,如果定義了針對Executor類型的插件,最終生成的Executor對象是被各個插件插入后的代理對象。
接下來,咱們才要真正的去了解sql的執(zhí)行過程了?;氐缴厦鎭砜矗覀兡玫搅?code>MapperProxy,調(diào)用Mapper接口的所有方法都會優(yōu)先調(diào)用到這個代理類的invoke方法(注意由于MyBatis中的Mapper接口沒有實現(xiàn)類,所以MpperProxy這個代理對象中沒有委托類,也就是說MapperProxy干了代理類和委托類的事情),具體是怎么做的,上源碼:
MapperProxy
MapperProxy的invoke方法本質(zhì)上是交給了MapperMethodInvoker,MapperMethodInvoker實質(zhì)就是封裝了一層MapperMethod。
/**
* 通過動態(tài)代理后,所有的Mapper方法調(diào)用都會走這個invoke方法
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 并不是任何一個方法都需要執(zhí)行調(diào)用代理對象進(jìn)行執(zhí)行,如果這個方法是Object中通用的方法(toString、hashCode等)無需執(zhí)行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// cachedInvoker封裝了一個MapperMethod對象
// 拆分出來更好理解
MapperMethodInvoker mapperMethodInvoker = cachedInvoker(method);
return mapperMethodInvoker.invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
MapperMethod
根據(jù)參數(shù)和返回值類型選擇不同的sqlSession來執(zhí)行。這樣mapper對象和sqlSession就真正關(guān)聯(lián)起來了
/**
* 這里其實就是先判斷CRUD的類型,根據(jù)類型去選擇到底執(zhí)行了sqlSession中的哪個方法
* @param sqlSession
* @param args
* @return
*/
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()) {
// 如果結(jié)果是map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 從字面意義上看,講轉(zhuǎn)換的參數(shù)放到sql里面
// Map<String, Object> param
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;
}
既然又回到SqlSession了,前面提到過,sqlsession只是一個門面,真正發(fā)揮作用的是executor,對sqlsession方法的訪問最終都會落到executor的相應(yīng)方法上去。Executor分成兩大類,一類是CacheExecutor,另一類是普通Executor。Executor的創(chuàng)建前面已經(jīng)介紹了,那么咱們就看看SqlSession的CRUD方法了,為了省事,還是就選擇其中的一個方法來做分析吧。這兒,咱們選擇了selectList方法。這里有寶寶就好奇了,從上面的代碼來看只有selectOne方法呢,哪里來的selectList。其實selectOne方法里面本質(zhì)就是調(diào)用了selectList方法。
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 轉(zhuǎn)而去調(diào)用selectList,很簡單的,如果得到0條則返回null,得到1條則返回1條,得到多條報TooManyResultsException錯
// 這里沒有查詢到結(jié)果的時候會返回null。因此一般建議mapper中編寫resultType使用包裝類型
List<T> list = this.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;
}
}
石錘了?。?!,接下來我們來看下selectList方法。
DefaultSqlSession
/**
*
* @param statement 全限定名稱+方法名 例如org.apache.ibatis.yyqtest.mapper.RoleMapper.testAnnotation
* @param parameter
* @param rowBounds
* @param handler
* @param <E>
* @return
*/
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 根據(jù)傳入的全限定命+方法名從映射的map中取出MappedStatement對象
MappedStatement ms = configuration.getMappedStatement(statement);
// 之類能看到CRUD實際上是交給Executor處理
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
?這里的statement是全限定名稱+方法名字,例如org.apache.ibatis.yyqtest.mapper.RoleMapper.testAnnotation,具體怎么來的,可以自己debug看下是怎么得到的。這里的getMapperStatement做了什么呢。話不多說上源碼。
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
?映射的語句,存在Map里,這里應(yīng)該是掃描配置文件的時候就已經(jīng)加進(jìn)去了 key是全限定名+方法名 value是對應(yīng)的mappedStatements。這個MapperStatements方法到底有什么作用呢,我們先別急。先回到executor.query的方法繼續(xù)往下走。
StatementHandler
可以看出,Executor本質(zhì)上也是個甩手掌柜,具體的事情原來是StatementHandler來完成的。當(dāng)Executor將指揮棒交給StatementHandler后,接下來的工作就是StatementHandler的事了。我們先看看StatementHandler是如何創(chuàng)建的:
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;
}
可以看到每次創(chuàng)建的StatementHandler都是RoutingStatementHandler,它只是一個分發(fā)者,他一個屬性delegate用于指定用哪種具體的StatementHandler。可選的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三種。選用哪種在mapper配置文件的每個statement里指定,默認(rèn)的是PreparedStatementHandler。同時還要注意到StatementHandler是可以被攔截器攔截的,和Executor一樣,被攔截器攔截后的對像是一個代理對象。
tatementHandler創(chuàng)建后需要執(zhí)行一些初始操作,比如statement的開啟和參數(shù)設(shè)置、對于PreparedStatement還需要執(zhí)行參數(shù)的設(shè)置操作等。代碼如下:
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;
}
這里呢會創(chuàng)建一個StatementHandler對象,這個對象中同時會 封裝ParameterHandler和ResultSetHandler對象。調(diào)用StatementHandler預(yù)編譯參數(shù) 以及設(shè)置參數(shù)值,使用ParameterHandler來給sql設(shè)置參數(shù)。
參數(shù)設(shè)置完畢后,執(zhí)行數(shù)據(jù)庫操作(update或query)。如果是query最后還有個查詢結(jié)果的處理過程。接下來,咱們看看StatementHandler 的一個實現(xiàn)類 PreparedStatementHandler(這也是我們最常用的,封裝的是PreparedStatement), 看看它使怎么去處理的:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 將處理好的結(jié)果交給了ResultSetHandler
return resultSetHandler.handleResultSets(ps);
}
到這里,一次sql的執(zhí)行流程就執(zhí)行完了!
簡單總結(jié)
獲取SqlSession流程
SqlSessuibFactoryBuilder解析配置文件,并將這些配置放到一個Configuration對象,這個對象中包含了MyBatis需要的所有配置,然后會用這個Configuration對象再去創(chuàng)建一個SqlSessionFactory對象。- 拿到
SqlSessionFactory對象后,會調(diào)用SqlSessionFactory的openSession方法,這個方法根據(jù)解析配置文件的事務(wù)和執(zhí)行器類型來創(chuàng)建一個執(zhí)行器,這個執(zhí)行器就是真正sql的執(zhí)行者。最后將執(zhí)行器和configuration封裝起來返回一個DefaultSqlSession - 拿到
SqlSession之后就能執(zhí)行各種CRUD的方法了。
Sql執(zhí)行流程
- 拿到
SqlSession之后調(diào)用getMapper方法,拿到Maper接口的代理對象MapperProxy,調(diào)用Mapper接口的所有方法都會調(diào)用MapperProxy的invoke方法。 - 到達(dá)invoke方法,就會掉用執(zhí)行器的executer方法。
- 往下,層層調(diào)下來會進(jìn)入
Executor組件(如果配置插件會對Executor進(jìn)行動態(tài)代 理)的query方法,這個方法中會創(chuàng)建一個StatementHandler對象,這個對象中同時會 封裝ParameterHandler和ResultSetHandler對象。調(diào)用StatementHandler預(yù)編譯參數(shù) 以及設(shè)置參數(shù)值,使用ParameterHandler來給sql設(shè)置參數(shù)。 - 調(diào)用
StatementHandler的增刪改查方法獲得結(jié)果,ResultSetHandler對結(jié)果進(jìn)行封 裝轉(zhuǎn)換,請求結(jié)束。
MyBatis一些重要的類
MapperRegistry: 本質(zhì)上是一個Map其中的key是Mapper接口的全限定名, value的MapperProxyFactory;MapperProxyFactory: 這個類是MapperRegistry中存的value值,在通過 sqlSession獲取Mapper時,其實先獲取到的是這個工廠,然后通過這個工廠創(chuàng)建 Mapper的動態(tài)代理類;MapperProxy: 實現(xiàn)了InvocationHandler接口,Mapper的動態(tài)代理接口方法的調(diào) 用都會到達(dá)這個類的invoke方法;MapperMethod: 判斷你當(dāng)前執(zhí)行的方式是增刪改查哪一種,并通過SqlSession執(zhí) 行相應(yīng)的操作;SqlSession: 作為MyBatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話,完成必 要數(shù)據(jù)庫增刪改查功能;Executor: MyBatis執(zhí)行器,是MyBatis 調(diào)度的核心,負(fù)責(zé)SQL語句的生成和查詢緩 存的維護(hù);StatementHandler: 封裝了JDBC Statement操作,負(fù)責(zé)對JDBC statement 的操作,如設(shè) 置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換成List集合。ParameterHandler: 負(fù)責(zé)對用戶傳遞的參數(shù)轉(zhuǎn)換成JDBC Statement 所需要的參數(shù)ResultSetHandler: 負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集對象轉(zhuǎn)換成List類型的集合;TypeHandler: 負(fù)責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換MappedStatement: MappedStatement維護(hù)了一條節(jié)點 的封裝SqlSource: 負(fù)責(zé)根據(jù)用戶傳遞的parameterObject,動態(tài)地生成SQL語句,將信息封裝到 BoundSql對象中,并返回BoundSql: 表示動態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息Configuration: MyBatis所有的配置信息都維持在Configuration對象之中。
到此這篇關(guān)于MyBatis 二級緩存實現(xiàn)機(jī)制的示例解析的文章就介紹到這了,更多相關(guān)MyBatis 二級緩存機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot+Redis執(zhí)行l(wèi)ua腳本的項目實踐
本文主要介紹了Springboot+Redis執(zhí)行l(wèi)ua腳本的項目實踐,詳細(xì)的介紹Redis與Lua腳本的結(jié)合應(yīng)用,具有一定的參考價值,感興趣的可以了解一下2023-09-09
MyBatis-Plus不使用數(shù)據(jù)庫默認(rèn)值的問題及解決
這篇文章主要介紹了MyBatis-Plus不使用數(shù)據(jù)庫默認(rèn)值的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07
eclipse自動創(chuàng)建SpringBoot項目報錯的解決
這篇文章主要介紹了eclipse自動創(chuàng)建SpringBoot項目報錯的解決方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
java使用DelayQueue實現(xiàn)延時任務(wù)
項目中經(jīng)常會用到類似一些需要延遲執(zhí)行的功能,比如緩存,java提供了DelayQueue來很輕松的實現(xiàn)這種功能,下面小編就來和大家介紹一下如何使用DelayQueue實現(xiàn)延時任務(wù)吧2023-10-10
spring boot加載第三方j(luò)ar包的配置文件的方法
本篇文章主要介紹了spring boot加載第三方j(luò)ar包的配置文件的方法,詳細(xì)的介紹了spring boot jar包配置文件的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10
mybatis查詢數(shù)據(jù)賦值到model里面為空的解決
這篇文章主要介紹了mybatis查詢數(shù)據(jù)賦值到model里面為空的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01

