MyBatis中一個SQL語句的執行過程解析


MyBatis 是一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。

 

平時用MyBatis框架開發時,配置好config.xml和mapper.xml映射文件和定義好java接口,就可以操作數據庫了,

當然也可以像spring一樣基於注解配置,看個人情況。

mybatis配置文件

mybatis mapper映射文件(由於模塊眾多,mapper映射文件很多,只列舉一個) 

然后只需定義好java接口,就能操作數據庫了


public interface MenuDao { //要和mapper.xml一一對應上 public List<Menu> findByParentIdsLike(Menu menu); public List<Menu> findByUserId(Menu menu); public int updateParentIds(Menu menu); public int updateSort(Menu menu); }

測試代碼:

/** * * @author dgm * 探究mybatis源碼 */ public class MyBatisTest { public static void main(String[] args) { // TODO Auto-generated method stub try { System.out.println("開始mybatis實驗"); UserDao userDao; SqlSession sqlSession; String resource = "conf/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); sqlSession = sqlSessionFactory.openSession(); userDao = sqlSession.getMapper(UserDao.class);//.selectUserById(1); System.out.println("其實是代理對象:"+userDao+",類型:"+userDao.getClass()); User user = userDao.selectUserById(1); //userDao = new MybatisDaoImpl(sqlSession); //user = userDao.selectUserById(2); System.out.println(user); System.out.println(userDao.getClass().getName()); user.setUsername("dongguangming"); //sqlSession.commit(); sqlSession.close(); System.out.println("結束mybatis實驗"); } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } }

執行結果:

 

實在簡單,我就簡單介紹下mybatis怎么一步一步運行起來的,下面才是重點

1.  xml文件解析階段,怎么把xml文件解析成Configuration對象(很重要的一個全局性對象)

1.1  MyBatis Config xml配置文件

它是一種符合DTD約束的一種配置文件,可以詳細看它如何規范的http://mybatis.org/dtd/mybatis-3-config.dtd

<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

具體表現可在config.xml配置文件中查看其組成部分  https://mybatis.org/mybatis-3/zh/configuration.html,現在流行注解,不知道能不能很好的表現這種結構,dom樹結構

Mybatis要把該xml文件解析成一個全局性的Configurationl類型的java對象,它的類定義諸多屬性,該文件在org.apache.ibatis.session包下

public class Configuration { protected Environment environment; protected boolean safeRowBoundsEnabled; protected boolean safeResultHandlerEnabled = true; protected boolean mapUnderscoreToCamelCase; protected boolean aggressiveLazyLoading; protected boolean multipleResultSetsEnabled = true; protected boolean useGeneratedKeys; protected boolean useColumnLabel = true; protected boolean cacheEnabled = true; protected boolean callSettersOnNulls; protected boolean useActualParamName = true; protected boolean returnInstanceForEmptyRow; protected String logPrefix; protected Class<? extends Log> logImpl; protected Class<? extends VFS> vfsImpl; protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected JdbcType jdbcTypeForNull = JdbcType.OTHER; protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")); protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; protected ResultSetType defaultResultSetType; protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; protected Properties variables = new Properties(); protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); protected ObjectFactory objectFactory = new DefaultObjectFactory(); protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); protected boolean lazyLoadingEnabled = false; protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL protected String databaseId; /** * Configuration factory class. * Used to create Configuration for loading deserialized unread properties. * * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a> */ protected Class<?> configurationFactory; protected final MapperRegistry mapperRegistry = new MapperRegistry(this); protected final InterceptorChain interceptorChain = new InterceptorChain(); protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this); protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); protected final Map<String, Cache> caches = new StrictMap<>("Caches collection"); protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection"); protected final Set<String> loadedResources = new HashSet<>(); protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers"); 。。。。。。

下面開始對配置文件進行處理,希望你有java和xml數據綁定的開發經歷,可以參考Java and XML Data Binding.pdf  https://github.com/dongguangming/java/blob/master/O'Reilly%20-%20Java%20and%20XML%20Data%20Binding.pdf

String resource = "conf/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream);

首先,我們使用 MyBatis 自帶的工具類 Resources 讀取加載配置文件,得到一個輸入流。

然后再通過 SqlSessionFactoryBuilder 對象的build方法構建 SqlSessionFactory 對象。

/** * 通過Configuration對象創建SqlSessionFactory * 然后調用該方法創建SqlSessionFactory */ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }

 從圖片上看到MyBatis 配置文件是通過XMLConfigBuilder進行parse()解析的,繼續一層一層往下看

 public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //調用解析方法,還記得xml文檔的根節點configuration嗎,直接子元素11個級別 parseConfiguration(parser.evalNode("/configuration")); return configuration; }

接着調用parseConfiguration(),剛好11個直接子節點,一個個分別解析處理

 這樣,一個 MyBatis 的解析過程就出來了,每個配置的解析邏輯都封裝在了相應的方法中。由於解析模塊眾多,我就選幾個了,要不然寫不完

1.1.1  解析 properties 配置

mysql.properties屬性文件,內容如下

#MySQL for mybatis
mybatis.driver=com.mysql.jdbc.Driver
mybatis.url=jdbc:mysql://192.168.8.200:3306/demo?useUnicode=true&characterEncoding=utf8&autoReconnect=true
mybatis.username=root
mybatis.password=123456
<properties resource="conf/mysql.properties" /> 

解析properties節點是由propertiesElement這個方法完成的,在上面的配置中, properties 節點配置了一個 resource 屬性。下面我們參照上面的配置,來分析一下 propertiesElement 的邏輯。

// -☆- XMLConfigBuilder private void propertiesElement(XNode context) throws Exception { if (context != null) { // 解析 propertis 的子節點,並將這些節點內容轉換為屬性對象 Properties Properties defaults = context.getChildrenAsProperties(); // 獲取 propertis 節點中的 resource 和 url 屬性值 String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); // 兩者都不用空,則拋出異常 if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { // 從文件系統中加載並解析屬性文件 defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { // 通過 url 加載並解析屬性文件 defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); // 將屬性值設置到 configuration 中 configuration.setVariables(defaults); } } public Properties getChildrenAsProperties() { Properties properties = new Properties(); // 獲取並遍歷子節點 for (XNode child : getChildren()) { // 獲取 property 節點的 name 和 value 屬性 String name = child.getStringAttribute("name"); String value = child.getStringAttribute("value"); if (name != null && value != null) { // 設置屬性到屬性對象中 properties.setProperty(name, value); } } return properties; } // -☆- XNode public List<XNode> getChildren() { List<XNode> children = new ArrayList<XNode>(); // 獲取子節點列表 NodeList nodeList = node.getChildNodes(); if (nodeList != null) { for (int i = 0, n = nodeList.getLength(); i < n; i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { // 將節點對象封裝到 XNode 中,並將 XNode 對象放入 children 列表中 children.add(new XNode(xpathParser, node, variables)); } } } return children; }

 properties 節點解析的主要過程主要包含三個步驟,一是解析 properties 節點的子節點,並將解析結果設置到 Properties 對象中。二是從文件系統或通過網絡讀取屬性配置,這取決於 properties 節點的 resource 和 url 是否為空。最后一步則是將解析出的屬性對象設置到 XPathParser 和 Configuration 對象中。

需要注意的是,propertiesElement 方法是先解析 properties 節點的子節點內容,后再從文件系統或者網絡讀取屬性配置,並將所有的屬性及屬性值都放入到 defaults 屬性對象中。這就會存在同名屬性覆蓋的問題,也就是從文件系統,或者網絡上讀取到的屬性及屬性值會覆蓋掉 properties 子節點中同名的屬性和及值。

 1.1.2 解析 settings 配置

1.1.2.1  settings 節點的解析過程

settings 配置是 MyBatis 中非常重要的配置,這些配置用於調整 MyBatis 運行時的行為。settings 配置繁多,在對這些配置不熟悉的情況下,保持默認配置即可。關於 settings 相關配置,MyBatis 官網上進行了比較詳細的描述,https://mybatis.org/mybatis-3/zh/configuration.html#settings,我就以我的配置舉例

<settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>

接下來,對照上面的配置,來分析源碼。如下:

// -☆- XMLConfigBuilder private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } // 獲取 settings 子節點中的內容,getChildrenAsProperties 方法前面已分析過,這里不再贅述 Properties props = context.getChildrenAsProperties(); // 創建 Configuration 類的“元信息”對象 MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { // 檢測 Configuration 中是否存在相關屬性,不存在則拋出異常 if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } return props; }

注意由於節點多,導致xml dom解析邏輯判斷也多,就不一一舉例細節了,但邏輯不是技術,只需要知道會生成一個Configurationl類型的java對象即可。

 

2.  SQL語句的執行流程

再次貼下代碼

            SqlSession sqlSession;

			String resource = "conf/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder() .build(inputStream); sqlSession = sqlSessionFactory.openSession(); MybatisDao ud= sqlSession.getMapper(MybatisDao.class); System.out.println("其實是代理對象:"+ud+",類型:"+ud.getClass()); User user = ud.selectUserById(1); System.out.println(user); 

     MybatisDao ud= sqlSession.getMapper(MybatisDao.class);會返回一個代理對象,繼續追蹤sqlSession.getMapper(MybatisDao.class)是如何實現的,

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); 。。。 try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }

 

而knowMappers實際上存放的是

 knownMappers.put(type, new MapperProxyFactory<>(type));

MapperProxyFactory定義如下 

public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } // @SuppressWarnings("unchecked") 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<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } 

MapperProxy定義如下,實現了InvocationHandler接口

public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } 

 

然后通過調用mapperProxyFactory.newInstance(sqlSession)返回代理對象

這下明白很多文章說為啥說mybatis只定義接口就能調用方法了。

此時控制台輸出

 

緊接着就是調用接口方法(注意是代理對象調用方法):User user = ud.selectUserById(1);

 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }

怎么拿方法不一一細看,又是一大堆邏輯,這里只看最后一句執行命令:mapperMethod.execute(sqlSession, args);

具體代碼如下

 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()) { 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; }

此例子中其實會執行select

result = sqlSession.selectOne(command.getName(), param);

session是DefaultSqlSession類型的,因為sqlSessionFactory默認生成的SqlSession是DefaultSqlSession類型。selectOne()會調用selectList()。

// DefaultSqlSession類 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // CURD操作是交給Excetor去處理的 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(); } }

如圖

在DefaultSqlSession.selectList中的各種CURD操作都是通多Executor進行的,這里executor的類型是CachingExecutor,接着跳轉到其中的query方法中。

// CachingExecutor 類 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); // 獲取綁定的sql命令,比如"SELECT * FROM xxx" CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

  getBoundSql為了獲取綁定的sql命令,在創建完cacheKey之后,就進入到CachingExecutor 類中的另一個query方法中。

// CachingExecutor 類 @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

  這里真正執行query操作的是SimplyExecutor代理來完成的,接着就進入到了SimplyExecutor的父類BaseExecutor的query方法中。

// SimplyExecutor的父類BaseExecutor類 @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; /** * localCache是一級緩存,如果找不到就調用queryFromDatabase從數據庫中查找 */ list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }

此時可以斷定是第一次SQL查詢操作,

所以會調用queryFromDatabase方法來執行查詢。

// SimplyExecutor的父類BaseExecutor類 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }

 從數據庫中查詢數據,調用doQuery方法,進入到SimplyExecutor中進行操作。

// SimplyExecutor類 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(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 子流程1:SQL查詢參數的設置 stmt = prepareStatement(handler, ms.getStatementLog()); // StatementHandler封裝了Statement // 子流程2:SQL查詢操作和結果集的封裝 return handler.<E>query(stmt); } finally { closeStatement(stmt); } }

特別注意,在prepareStatement方法中會進行SQL查詢參數的設置,也就是咱們最開始傳遞進來的參數,其值為1。handler.<E>query(stmt)方法中會進行實際的SQL查詢操作和結果集的封裝(封裝成Java對象)。

prepareStatement方法階段(即設置SQL查詢參數)

// SimplyExecutor類 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 獲取一個Connection Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); // 設置SQL查詢中的參數值 return stmt; }

通過getConnection方法來獲取一個Connection,調用prepare方法來獲取一個Statement(這里的handler類型是RoutingStatementHandler,RoutingStatementHandler的prepare方法調用的是PrepareStatementHandler的prepare方法,因為PrepareStatementHandler並沒有覆蓋其父類的prepare方法,其實最后調用的是BaseStatementHandler中的prepare方法。是)。調用parameterize方法來設置SQL的參數值(這里最后調用的是PrepareStatementHandler中的parameterize方法,而PrepareStatementHandler.parameterize方法調用的是DefaultParameterHandler中的setParameters方法)。

// PrepareStatementHandler類 @Override public void parameterize(Statement statement) throws SQLException { parameterHandler.setParameters((PreparedStatement) statement); } // DefaultParameterHandler類 @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); } } } } }

此時已經給Statement設置了最初傳遞進去的參數。

那么接着分析流程2:

handler.<E>query(stmt)方法階段(SQL查詢及結果集的設置)

// RoutingStatementHandler類 @Override public <E> List<E> query(Statement statement) throws SQLException { return delegate.<E>query(statement); } // RoutingStatementHandler類 @Override public <E> List<E> query(Statement statement) throws SQLException { // 這里就到了熟悉的PreparedStatement了 PreparedStatement ps = (PreparedStatement) statement; // 執行SQL查詢操作 ps.execute(); // 結果交給ResultHandler來處理 return resultSetHandler.<E> handleResultSets(ps); } // DefaultResultSetHandler類(封裝返回值,將查詢結果封裝成Object對象) @Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); }

 

ResultSetWrapper是ResultSet的包裝類,調用getFirstResultSet方法獲取第一個ResultSet,同時獲取數據庫的MetaData數據,包括數據表列名、列的類型、類序號等,這些信息都存儲在ResultSetWrapper類中了。然后調用handleResultSet方法來來進行結果集的封裝。

// DefaultResultSetHandler類 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }

  這里調用handleRowValues方法來進行值的設置:

// DefaultResultSetHandler類 public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { // 封裝數據 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } } // 封裝數據,DefaultResultSetHandler類 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>(); skipRows(rsw.getResultSet(), rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap); storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } // DefaultResultSetHandler類 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // createResultObject為新創建的對象,數據表對應的類 Object resultObject = createResultObject(rsw, resultMap, lazyLoader, null); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(resultObject); boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty(); if (shouldApplyAutomaticMappings(resultMap, false)) { // 這里把數據填充進去,metaObject中包含了resultObject信息 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; resultObject = foundValues ? resultObject : null; return resultObject; } return resultObject; } // DefaultResultSetHandler類(把ResultSet中查詢結果填充到JavaBean中) private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (autoMapping.size() > 0) { // 這里進行for循環調用,因為user表中總共有7項,所以也就調用7次 for (UnMappedColumnAutoMapping mapping : autoMapping) { // 這里將esultSet中查詢結果轉換為對應的實際類型 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value); } } } return foundValues; } 

 

mapping.typeHandler.getResult會獲取查詢結果值的實際類型,比如我們user表中id字段為int類型,那么它就對應Java中的Integer類型,然后通過調用statement.getInt("id")來獲取其int值,其類型為Integer。metaObject.setValue方法會把獲取到的Integer值設置到Java類中的對應字段。

// MetaObject類 public void setValue(String name, Object value) { PropertyTokenizer prop = new PropertyTokenizer(name); if (prop.hasNext()) { MetaObject metaValue = metaObjectForProperty(prop.getIndexedName()); if (metaValue == SystemMetaObject.NULL_META_OBJECT) { if (value == null && prop.getChildren() != null) { // don't instantiate child path if value is null return; } else { metaValue = objectWrapper.instantiatePropertyValue(name, prop, objectFactory); } } metaValue.setValue(prop.getChildren(), value); } else { objectWrapper.set(prop, value); } }

  metaValue.setValue方法最后會調用到Java類中對應數據域的set方法,這樣也就完成了SQL查詢結果集的Java類封裝過程。

 

至此,分析完畢。和spring有點類似,只是方向不一樣,都是大量的dom解析(現在是java注解比較多了)成全局java文件,然后結合邏輯編碼實現相應的功能。

 

但記住:邏輯往往不是技術,如何構思、組裝才是重點!!!

 

注意: 你如果只是想使用mybatis(寫下配置文件和mapper文件)就不需要看此文了,也沒什么必要!

課題:留給你們一個分頁插件,構思設想和編碼如何實現???

 

后記

請你們務必靈活運用這些器:分發器,過濾器,攔截器,監聽器,反應堆器,特別注意這跟語言、框架無關。

當然還有操作系統相關知識,希望其他人早意識到cpu、內存分配、io模型、進程/線程等是很重要的(會讓你更好的理解一些庫比如libevent和中間件的實現原理),也跟語言、庫、框架無關,而不管你用java、scala還是golang實現。

 

因為很少有網課關於操作系統和網絡的系統化培訓,那還是讓我董廣明告訴要學什么,如下

可能市面上做crud的開發者居多,那數據庫要留意下。

 

 

已把mybatis電子書上傳,很簡單 https://github.com/dongguangming/java/blob/master/MyBatis/Java%20Persistence%20with%20MyBatis%203(%E4%B8%AD%E6%96%87%E7%89%88).pdf

 

參考:

    0.  MyBatis事務   https://blog.csdn.net/dong19891210/article/details/105672535

  1. SpringBoot : Working with MyBatis https://www.sivalabs.in/2016/03/springboot-working-with-mybatis/

  2. mybatis配置 https://mybatis.org/mybatis-3/zh/configuration.html

  3. Mybatis source code analysis https://developpaper.com/mybatis-source-code-analysis/

  4. Mybatis Source Code Analysis https://programming.vip/docs/mybatis-source-code-analysis.html

  5. MyBatis source code analysis https://programmer.group/mybatis-source-code-analysis.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM