zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    《ASP.NET Core跨平台开发从入门到实战》Web API自定义格式化protobuf
    .NET Core中文分词组件jieba.NET Core
    .NET Core 2.0及.NET Standard 2.0
    Visual Studio 2017 通过SSH 调试Linux 上.NET Core
    Visual Studio 2017 ASP.NET Core开发
    Visual Studio 2017正式版离线安装及介绍
    在.NET Core 上运行的 WordPress
    IT人员如何开好站立会议
    puppeteer(二)操作实例——新Web自动化工具更轻巧更简单
    puppeteer(一)环境搭建——新Web自动化工具(同selenium)
  • 原文地址:https://www.cnblogs.com/dongguangming/p/13842190.html
Copyright © 2011-2022 走看看