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