摘要:的解析和運行原理構(gòu)建過程提供創(chuàng)建的核心接口。在構(gòu)造器初始化時會根據(jù)和的方法解析為命令。數(shù)據(jù)庫會話器定義了一個對象的適配器,它是一個接口對象,構(gòu)造器根據(jù)配置來適配對應(yīng)的對象。它的作用是給實現(xiàn)類對象的使用提供一個統(tǒng)一簡易的使用適配器。
MyBatis的解析和運行原理 構(gòu)建SqlSessionFactory過程
SqlSessionFactory提供創(chuàng)建MyBatis的核心接口SqlSession。MyBatis采用構(gòu)造模式去創(chuàng)建SqlSessionFactory,我們可以通過SqlSessionFactoryBuilder去構(gòu)建。
第一步,通過XMLConfigBuilder解析配置的XML文件,讀出配置參數(shù),并將讀取的數(shù)據(jù)存入這個Configuration類中。
第二步,使用Configuration對象去創(chuàng)建SqlSessionFactory。
SqlSessionFactoryBuilder的源碼:
public class SqlSessionFactoryBuilder { ..... public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // XMLConfigBuilder解析配置的XML文件,構(gòu)建Configuration return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } // 使用Configuration對象去創(chuàng)建SqlSessionFactory public SqlSessionFactory build(Configuration config) { // SqlSessionFactory是一個接口,為此MyBatis提供了一個默認實現(xiàn)類 return new DefaultSqlSessionFactory(config); } }構(gòu)建Configuration
在XMLConfigBuilder中,MyBatis會讀出所有XML配置的信息,然后將這些信息保存到Configuration類的單例中。
它會做如下初始化:
properties全局參數(shù)
setting設(shè)置
typeAliases別名
typeHandler類型處理器
ObjectFactory對象
plugin插件
environment環(huán)境
DatabaseIdProvider數(shù)據(jù)庫標(biāo)識
Mapper映射器
XMLConfigBuilder的源碼:
public class XMLConfigBuilder extends BaseBuilder { ... public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 解析配置文件,設(shè)置Configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { // 讀出MyBatis配置文件中的configuration下的各個子標(biāo)簽元素 // 把全部信息保存到Configuration類的單例中 try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 設(shè)置mapper映射器 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } }映射器的內(nèi)部組成
XMLMapperBuilder負責(zé)對配置文件中的Mapper映射器進行解析,其中在configurationElement方法中可以看出來,會分別對配置文件中的parameterMap、resultMap、sql、select|insert|update|delete元素進行解析。
public class XMLMapperBuilder extends BaseBuilder { public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析配置文件中的mapper映射器 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper"s namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); // 解析我們配置的parameterMap元素 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析我們配置的resultMap元素 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析我們配置的sql元素 sqlElement(context.evalNodes("/mapper/sql")); // 解析我們配置的select、insert、update、delete元素 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } }
在方法buildStatementFromContext()中,會根據(jù)配置信息創(chuàng)建一個MappedStatement對象。
MappedStatement,它保存映射器的一個節(jié)點(select|insert|update|delete)。包括許多我們配置的SQL、SQL的id、緩存信息、resultMap、parameterType、resultType、languageDriver等重要配置內(nèi)容。
public final class MappedStatement { private Configuration configuration; private String id; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private ListresultMaps; private boolean flushCacheRequired; private boolean useCache; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String databaseId; private LanguageDriver lang; ...... }
SqlSource,它是提供BoundSql對象的地方,它是MappedStatement的一個屬性。
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
BoundSql,它是建立SQL和參數(shù)的地方。
public class BoundSql { private final String sql; private final ListSqlSession運行過程parameterMappings; private final Object parameterObject; private final Map additionalParameters; private final MetaObject metaParameters; }
SqlSession是一個接口,在MyBatis中有一個默認實現(xiàn)DefaultSqlSession。我們構(gòu)建SqlSessionFactory就可以輕易地拿到SqlSession了。通過SqlSession,我們拿到Mapper,之后可以做查詢、插入、更新、刪除的方法。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
其實getMapper()方法拿到的mapper是通過Java動態(tài)代理實現(xiàn)的。從getMapper()方法逐級往下看,可以發(fā)現(xiàn)在MapperRegistry類的getMapper()方法中會拿到一個MapperProxyFactory的對象,最后是通過MapperProxyFactory對象去生成一個Mapper的。
public class DefaultSqlSession implements SqlSession { ..... @Override public映射器的動態(tài)代理T getMapper(Class type) { return configuration. getMapper(type, this); } } public class Configuration { ..... public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { ...... public T getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } }
Mapper映射是通過動態(tài)代理實現(xiàn)的,MapperProxyFactory用來生成動態(tài)代理對象。
public class MapperProxyFactory{ ...... protected T newInstance(MapperProxy mapperProxy) { // 動態(tài)代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
在MapperProxyFactory的newInstance方法中可以看到有一個MapperProxy對象,MapperProxy實現(xiàn)InvocationHandler接口(動態(tài)代理需要實現(xiàn)這一接口)的代理方法invoke(), 這invoke()方法實現(xiàn)對被代理類的方法進行攔截。
而在invoke()方法中,MapperMethod對象會執(zhí)行Mapper接口的查詢或其他方法。
public class MapperProxyimplements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 先判斷是否一個類,在這里Mapper顯然是一個接口 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); // 判斷是不是接口默認實現(xiàn)方法 } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 緩存中取出MapperMethod,不存在的話,則根據(jù)Configuration初始化一個 final MapperMethod mapperMethod = cachedMapperMethod(method); // 執(zhí)行Mapper接口的查詢或其他方法 return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
MapperMethod采用命令模式運行,并根據(jù)上下文跳轉(zhuǎn)。MapperMethod在構(gòu)造器初始化時會根據(jù)Configuration和Mapper的Method方法解析為SqlCommand命令。之后在execute方法,根據(jù)SqlCommand的Type進行跳轉(zhuǎn)。然后采用命令模式,SqlSession通過SqlCommand執(zhí)行插入、更新、查詢、選擇等方法。
public MapperMethod(Class> mapperInterface, Method method, Configuration config) { // 根據(jù)Configuration和Mapper的Method方法解析為SqlCommand this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根據(jù)Type進行跳轉(zhuǎn),通過sqlSession執(zhí)行相關(guān)的操作 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 { Object param = method.convertArgsToSqlCommandParam(args); 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; }
看到這里,應(yīng)該大概知道了MyBatis為什么只用Mapper接口便能夠運行SQL,因為映射器的XML文件的命名空間namespace對應(yīng)的便是這個接口的全路徑,那么它根據(jù)全路徑和方法名便能夠綁定起來,通過動態(tài)代理技術(shù),讓這個接口跑起來。而后采用命令模式,最后還是使用SqlSession接口的方法使得它能夠執(zhí)行查詢,有了這層封裝我們便可以使用這個接口編程。
不過還是可以看到,最后插入、更新、刪除、查詢操作還是會回到SqlSession中進行處理。
我們已經(jīng)知道了映射器其實就是一個動態(tài)代理對象,進入到了MapperMethod的execute方法。它經(jīng)過簡單判斷就是進入了SqlSession的刪除、更新、插入、選擇等方法。sqlSession執(zhí)行一個查詢操作。可以看到是通過一個executor來執(zhí)行的。
其實SqlSession中的Executor執(zhí)行器負責(zé)調(diào)度StatementHandler、ParameterHandler、ResultHandler等來執(zhí)行相關(guān)的SQL。
StatementHandler:使用數(shù)據(jù)庫的Statement(PrepareStatement)執(zhí)行操作
ParameterHandler:用于SQL對參數(shù)的處理
ResultHandler:進行最后數(shù)據(jù)集(ResultSet)的封裝返回處理
Sqlsession其實是一個接口,它有一個DefaultSqlSession的默認實現(xiàn)類。
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; // Executor執(zhí)行器,負責(zé)調(diào)度SQL的執(zhí)行 private final Executor executor; ...... @Override publicExecutor執(zhí)行器List selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // 通過executor執(zhí)行查詢操作 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(); } } }
執(zhí)行器起到了至關(guān)重要的作用,它是一個真正執(zhí)行Java和數(shù)據(jù)庫交互的東西。在MyBatis中存在三種執(zhí)行器,我們可以在MyBatis的配置文件中進行選擇。
SIMPLE,簡易執(zhí)行器
REUSE,是一種執(zhí)行器重用預(yù)處理語句
BATCH,執(zhí)行器重用語句和批量更新,她是針對批量專用的執(zhí)行器
它們都提供了查詢和更新方法,以及相關(guān)的事務(wù)方法。
Executor是通過Configuration類創(chuàng)建的,MyBatis將根據(jù)配置類型去確定你需要創(chuàng)建三種執(zhí)行器中的哪一種。
public class Configuration { ...... 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); } // MyBatis插件,構(gòu)建一層層的動態(tài)代理對象 // 在調(diào)度真實的方法之前執(zhí)行配置插件的代碼 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
顯然MyBatis根據(jù)Configuration來構(gòu)建StatementHandler,然后使用prepareStatement方法,對SQL編譯并對參數(shù)進行初始化,resultHandler再組裝查詢結(jié)果返回給調(diào)用者來完成一次查詢。
public class SimpleExecutor extends BaseExecutor { ..... @Override publicStatementHandler數(shù)據(jù)庫會話器List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); // 根據(jù)Configuration來構(gòu)建StatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 對SQL編譯并對參數(shù)進行初始化 stmt = prepareStatement(handler, ms.getStatementLog()); // 組裝查詢結(jié)果返回給調(diào)用者 return handler. query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); // 進行預(yù)編譯和基礎(chǔ)設(shè)置 stmt = handler.prepare(connection, transaction.getTimeout()); // 設(shè)置參數(shù) handler.parameterize(stmt); return stmt; } }
StatementHandler就是專門處理數(shù)據(jù)庫會話的。
創(chuàng)建StatementHandler:
public class 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); // MyBatis插件,生成一層層的動態(tài)代理對象 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } }
RoutingStatementHandler其實不是我們真實的服務(wù)對象,它是通過適配模式找到對應(yīng)的StatementHandler來執(zhí)行。
StatementHandler分為三種:
SimleStatementHandler
PrepareStatementHandler
CallableStatementHandler
在初始化RoutingStatementHandler對象的時候它會根據(jù)上下文環(huán)境來決定創(chuàng)建哪個StatementHandler對象。
public class RoutingStatementHandler implements StatementHandler { ...... private final StatementHandler delegate; 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()); } } }
數(shù)據(jù)庫會話器定義了一個對象的適配器delegate,它是一個StatementHandler接口對象,構(gòu)造器根據(jù)配置來適配對應(yīng)的StatementHandler對象。它的作用是給實現(xiàn)類對象的使用提供一個統(tǒng)一、簡易的使用適配器。此為對象的適配模式,可以讓我們使用現(xiàn)有的類和方法對外提供服務(wù),也可以根據(jù)實際的需求對外屏蔽一些方法,甚至加入新的服務(wù)。
在執(zhí)行器Executor執(zhí)行查詢操作的時候,我們看到PreparedStatementHandler的三個方法:prepare、parameterize和query。
public abstract class BaseStatementHandler implements StatementHandler { ..... @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // 對SQL進行了預(yù)編譯 statement = instantiateStatement(connection); setStatementTimeout(statement, transactionTimeout); 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 { @Override public void parameterize(Statement statement) throws SQLException { // 設(shè)置參數(shù) parameterHandler.setParameters((PreparedStatement) statement); } @Override protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { return connection.prepareStatement(sql); } } @Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執(zhí)行SQL ps.execute(); // resultSetHandler封裝結(jié)果返回 return resultSetHandler. handleResultSets(ps); } }
一條查詢SQL的執(zhí)行過程,Executor會先調(diào)用StatementHandler的prepare()方法預(yù)編譯SQL語句,同時設(shè)置一些基本運行的參數(shù)。然后用parameterize()方法啟動ParameterHandler設(shè)置參數(shù),完成預(yù)編譯,跟著就是執(zhí)行查詢。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/69531.html
摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點點滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進階面試問題列表 -...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進步歡迎點贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
摘要:插件插件接口在中使用插件,我們必須實現(xiàn)接口。它將直接覆蓋你所攔截對象原有的方法,因此它是插件的核心方法。插件在對象中的保存插件的代理和反射設(shè)計插件用的是責(zé)任鏈模式,的責(zé)任鏈?zhǔn)怯扇ザx的。 插件 1、插件接口 在MyBatis中使用插件,我們必須實現(xiàn)接口Interceptor。 public interface Interceptor { // 它將直接覆蓋你所攔截對象原有的方法,因...
摘要:目錄其中每個章節(jié)知識點都是相關(guān)連由淺入深的一步步全面分析了技術(shù)原理以及實戰(zhàn)由于文案較長想深入學(xué)習(xí)以及對于該文檔感興趣的朋友們可以加群免費獲取。這些場景在大量的編碼中使用,具備較強的實用價值,這些內(nèi)容都是通過實戰(zhàn)得來的,供讀者們參考。 前言系統(tǒng)掌握MyBatis編程技巧已經(jīng)成了用Java構(gòu)建移動互聯(lián)網(wǎng)網(wǎng)站的必要條件 本文主要講解了Mybatis的應(yīng)用,解析了其原理,從而形成一個完整的知識...
閱讀 3328·2023-04-25 16:25
閱讀 3856·2021-11-15 18:01
閱讀 1614·2021-09-10 11:21
閱讀 3021·2021-08-02 16:53
閱讀 3090·2019-08-30 15:55
閱讀 2496·2019-08-29 16:24
閱讀 2107·2019-08-29 13:14
閱讀 1039·2019-08-29 13:00