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

  • 相关阅读:
    抽象类和接口的区别
    Overload和Override的区别
    final和finally和finalize的区别
    C#设计模式学习笔记:简单工厂模式(工厂方法模式前奏篇)
    C#设计模式学习笔记:(1)单例模式
    C#设计模式学习笔记:设计原则
    C#加密与解密(DESRSA)学习笔记
    C# IO流与文件读写学习笔记
    C#序列化与反序列化学习笔记
    C#索引器学习笔记
  • 原文地址:https://www.cnblogs.com/dongguangming/p/13842190.html
Copyright © 2011-2022 走看看