美文网首页SSMmybatis
mybatis 执行流程(3)

mybatis 执行流程(3)

作者: 晴天哥_王志 | 来源:发表于2018-07-17 19:54 被阅读242次

    开篇

     这篇文章的主要目的是为了讲清楚Mybatis的整个执行流程,会通过源码、流程图等多个维度进行说明,相关的细节由于涉及面比较广这里暂时先不详细展开。
     整体的思路先让大家有个宏观的概念,由了这个主轴以后我们再针对主轴上每个节点再进行细节分析,顺藤摸瓜,让我们先找到这个贯穿的藤。

    Mybatis的使用模板

     首先我们使用Mybatis的时候都有一般固定的套路:

    • 定义map接口,定义对外查询sql的接口。
    • 配置xml文件,定义查询sql语句。
    • 引入xml文件,引入xml文件进行解析。
    • 调用map接口,调用查询接口提供查询服务。



    1、新建一个com.kang.mapper的包,定义map接口,这里以UserMapper为例

    package com.kang.mapper;  
    import java.util.List;  
    import com.kang.pojo.User;  
    public interface UserMapper {  
        //根据用户id查询用户信息  
        public User findUserById(int id) throws Exception;  
        //查询用户列表  
        public List<User> findUserByUsername(String username) throws Exception;  
        //添加用户信息  
        public void insertUser(User user)throws Exception;   
    }  
    

    2、配置xml文件 UserMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>  
    <!DOCTYPE mapper  
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
    <mapper namespace="com.kang.mapper.UserMapper">  
    <!-- 注意这里的 namespace必须对应着map接口的全类名-->  
        <select id="findUserById" parameterType="int" resultType="user">  
            select * from user where id = #{id}  
        </select>  
    
        <select id="findUserByUsername" parameterType="java.lang.String"  
            resultType="user">  
            select * from user where username like '%${value}%'  
        </select>  
    
        <insert id="insertUser" parameterType="user">  
            <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">  
                select LAST_INSERT_ID()  
            </selectKey>  
            insert into user(username,birthday,sex,address)  
            values(#{username},#{birthday},#{sex},#{address})  
        </insert>  
    </mapper>  
    
    

    3、在SqlMapConfig.xml中加入映射文件

    <!-- 加载 映射文件 -->  
    <mappers>  
      <mapper resource="map/UserMapper.xml" />  
    </mappers>  
    
    

    4、调用方法

    //获取session  
            SqlSession session = sqlSessionFactory.openSession();  
            //获取mapper接口的代理对象  
            UserMapper userMapper = session.getMapper(UserMapper.class);  
            //调用代理对象方法  
            User user = userMapper.findUserById(27);  
            System.out.println(user);  
            //关闭session  
            session.close();  
            System.out.println("---------执行完毕-----------");  
    

    Mybatis执行过程

    Mybatis执行过程流程图

    Mybatis执行流程图.png

    Mybatis执行过程时序图

    Mybatis执行时序图.png

    Mybatis执行过程源码分析

    step1-DefaultSqlSession获取代理类

    UserMapper userMapper = session.getMapper(UserMapper.class)
    
    --------------------DefaultSqlSession.java------------------------------
      public <T> T getMapper(Class<T> type) {
        //最后会去调用MapperRegistry.getMapper
        return configuration.<T>getMapper(type, this);
      }
    
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    

    step2-MapperRegistry类生成代理类

     从代码可以看出来通过addMapper方法添加knownMappers,这个方法是在解析xml配置中被调用。

    ------------------------MapperRegistry.java-----------------------------------------
      //返回代理类
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
        if (mapperProxyFactory == null) {
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        }
        try {
          return mapperProxyFactory.newInstance(sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }
    
    
      //我们在解析XML文件的时候添加了如何添加一个映射
      public <T> void addMapper(Class<T> type) {
        //mapper必须是接口!才会添加
        if (type.isInterface()) {
          if (hasMapper(type)) {
            //如果重复添加了,报错
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();
            loadCompleted = true;
          } finally {
            //如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    

    step3-MapperProxyFactory生成代理类MapperProxy

    --------------------MapperProxyFactory.java------------------------------------------
    /**
     * 映射器代理工厂
     */
    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    
      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) {
        //用JDK自带的动态代理生成映射器
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }
    

    step4-MapperProxy通过invoke调用真正的接口

     MapperProxy的invoke当中执行method.invoke(this, args)调用真正的SQL查询接口,method就是查询的SQL接口的MapperMethod封装。

    
    ---------------------MapperProxy.java-----------------------
    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 {
        //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
        //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
        if (Object.class.equals(method.getDeclaringClass())) {
          try {
            return method.invoke(this, args);
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
        //这里优化了,去缓存中找MapperMethod
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行
        return mapperMethod.execute(sqlSession, args);
      }
    
      //去缓存中找MapperMethod
      private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
          //找不到才去new
          mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
          methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
      }
    
    }
    

    step5-MapperMethod的execute过程

     MapperMethod我们暂时只关注result = sqlSession.selectOne(command.getName(), param)过程,command.getName()返回的是在Mybatis的mapper.xml文件对应的接口文件当中定义的。

    --------------------------MapperMethod.java---------------------------------
      //执行
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
        if (SqlCommandType.INSERT == command.getType()) {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.insert(command.getName(), param));
        } else if (SqlCommandType.UPDATE == command.getType()) {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.update(command.getName(), param));
        } else if (SqlCommandType.DELETE == command.getType()) {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.delete(command.getName(), param));
        } else if (SqlCommandType.SELECT == command.getType()) {
          if (method.returnsVoid() && method.hasResultHandler()) {
            //如果有结果处理器
            executeWithResultHandler(sqlSession, args);
            result = null;
          } else if (method.returnsMany()) {
            //如果结果有多条记录
            result = executeForMany(sqlSession, args);
          } else if (method.returnsMap()) {
            //如果结果是map
            result = executeForMap(sqlSession, args);
          } else {
            //否则就是一条记录
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
          }
        } else {
          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;
      }
    

    step6-DefaultSqlSession的执行过程

     selectList当中通过executor.query()方法调用进入到executor当中执行。

    ------------------------DefaultSqlSession.java-----------------------------------
      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        //转而去调用selectList,很简单的,如果得到0条则返回null,得到1条则返回1条,得到多条报TooManyResultsException错
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
          return list.get(0);
        } else if (list.size() > 1) {
          throw new TooManyResultsException("Expected one result (or null) 
                                             to be returned by selectOne(), but found: " + list.size());
        } else {
          return null;
        }
      }
    
      @Override
      public <E> List<E> selectList(String statement, Object parameter) {
        return this.selectList(statement, parameter, RowBounds.DEFAULT);
      }
    
      //核心selectList
      @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          //根据statement id找到对应的MappedStatement
          MappedStatement ms = configuration.getMappedStatement(statement);
          //转而用执行器来查询结果,注意这里传入的ResultHandler是null
          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();
        }
      }
    

    step7-BaseExecutor的执行过程

     在executor的执行过程中,首先调用的是BaseExecutor的接口,Executor的类关系图参加文章末尾。

    --------------------------BaseExecutor.java------------------------------------------
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) 
                     throws SQLException {
        //得到绑定sql
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
                throws SQLException {
        //得到绑定sql
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //查询
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }
    
    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.");
        }
        //先清局部缓存,再查询.但仅查询堆栈为0,才清。为了处理递归调用
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
          clearLocalCache();
        }
        List<E> list;
        try {
          //加一,这样递归调用到上面的时候就不会再清局部缓存了
          queryStack++;
          //先根据cachekey从localCache去查
          list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
          if (list != null) {
            //若查到localCache缓存,处理localOutputParameterCache
            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
            //如果是STATEMENT,清本地缓存
            clearLocalCache();
          }
        }
        return list;
      }
    
      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);
        //如果是存储过程,OUT参数也加入缓存
        if (ms.getStatementType() == StatementType.CALLABLE) {
          localOutputParameterCache.putObject(key, parameter);
        }
        return list;
      }
    

    step8-SimpleExecutor的执行过程

     我们这里暂且以SimpleExecutor为例进行分析,其他还有ReuseExecutor和BatchExecutor。通过handler.<E>query(stmt, resultHandler)调用SimpleStatementHandler。
     在StatementHandler的创建过程中长链路调用过程中我们创建了ParameterHandler。

    ----------------------SimpleExecutor.java---------------------------------------
      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
          //这里看到ResultHandler传入了,这个StatementHandler 是RoutingStatementHandler
          StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
          //准备语句,transaction获取connection,connection创建statement。
          stmt = prepareStatement(handler, ms.getStatementLog());
          //StatementHandler.query
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }
    
    
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, 
    Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        //创建路由选择语句处理器
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, 
                                                                        parameterObject, rowBounds, resultHandler, boundSql);
        //插件在这里插入
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    
    
    
      public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, 
                                     ResultHandler resultHandler, BoundSql boundSql) {
    
        //根据语句类型,委派到不同的语句处理器(STATEMENT|PREPARED|CALLABLE)
        switch (ms.getStatementType()) {
          case STATEMENT:
            delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case PREPARED:
            delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          case CALLABLE:
            delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            break;
          default:
            throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
        }
      }
    
    
    public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, 
                                  RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
      }
    
    
    protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, 
                                   RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        this.configuration = mappedStatement.getConfiguration();
        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
    
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = configuration.getObjectFactory();
    
        if (boundSql == null) { // issue #435, get the key before calculating the statement
          generateKeys(parameterObject);
          boundSql = mappedStatement.getBoundSql(parameterObject);
        }
    
        this.boundSql = boundSql;
    
        //生成parameterHandler
        this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, 
    
    boundSql);
        //生成resultSetHandler
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, 
    parameterHandler, resultHandler, boundSql);
      }
    

    step9-StatementHandler的执行过程

     这里我们以SimpleStatementHandler为例进行说明,BaseStatementHandler的具体实现类还包括 CallableStatementHandler和PreparedStatementHandler。

    ---------------------------------SimpleStatementHandler.java---------------------------
    
      //select-->结果给ResultHandler
      @Override
      public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        //这里感觉调用jdbc的接口完成查询,待研究
        statement.execute(sql);
        //先执行Statement.execute,然后交给ResultSetHandler.handleResultSets
        return resultSetHandler.<E>handleResultSets(statement);
      }
    

    step10-ResultSetHandler的执行过程

     ResultSetHandler的实现类只有DefaultResultSetHandler。

    ---------------------------DefaultResultSetHandler.java---------------------------
    
    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);
    
        // 处理 getResultMaps
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        //一般resultMaps里只有一个元素
        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++;
        }
    
        //处理 getResulSets
        String[] resultSets = mappedStatement.getResulSets();
        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);
      }
    

    类关系图

    StatementHandler.png Executor.png

    参考文档

    Mybatis中Mapper动态代理的实现原理
    Mybatis3.3.x技术内幕(十一):执行一个Sql命令的完整流程

    相关文章

      网友评论

        本文标题:mybatis 执行流程(3)

        本文链接:https://www.haomeiwen.com/subject/uucppftx.html