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í)行過程了。回到上面來看,我們拿到了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
??蛇x的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-09MyBatis-Plus不使用數(shù)據(jù)庫默認(rèn)值的問題及解決
這篇文章主要介紹了MyBatis-Plus不使用數(shù)據(jù)庫默認(rèn)值的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07eclipse自動創(chuàng)建SpringBoot項目報錯的解決
這篇文章主要介紹了eclipse自動創(chuàng)建SpringBoot項目報錯的解決方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01java使用DelayQueue實現(xiàn)延時任務(wù)
項目中經(jīng)常會用到類似一些需要延遲執(zhí)行的功能,比如緩存,java提供了DelayQueue來很輕松的實現(xiàn)這種功能,下面小編就來和大家介紹一下如何使用DelayQueue實現(xiàn)延時任務(wù)吧2023-10-10spring boot加載第三方j(luò)ar包的配置文件的方法
本篇文章主要介紹了spring boot加載第三方j(luò)ar包的配置文件的方法,詳細(xì)的介紹了spring boot jar包配置文件的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-10-10mybatis查詢數(shù)據(jù)賦值到model里面為空的解決
這篇文章主要介紹了mybatis查詢數(shù)據(jù)賦值到model里面為空的解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01