• MyBatis源码分析(四):SQL执行过程分析


    一、获取Mapper接口的代理

    根据上一节,Mybatis初始化之后,利用sqlSession(defaultSqlSession)的getMapper方法获取Mapper接口

    1 @Override
    2 public <T> T getMapper(Class<T> type) {
    3     return configuration.<T>getMapper(type, this);
    4 }

    而调用configuration对象的getMapper方法

    1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    2     return mapperRegistry.getMapper(type, sqlSession);
    3 }

    再次调用mapperRegister,注册mapper的类

     1 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     2     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
     3     if (mapperProxyFactory == null) {
     4       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
     5     }
     6     try {
     7       return mapperProxyFactory.newInstance(sqlSession);
     8     } catch (Exception e) {
     9       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    10     }
    11   }

    而mapperRegister根据传进来的mapper接口来创建MapperProxyFactory代理工厂对象,再用sqlSession参数创建Mapper的代理对象,这里运用的是JDK的动态代理,Proxy.newProxyInstance方法绑定mapper接口,第一个参数是类加载器,第二个参数是需要实现的接口数组,第三个是InvocationHandler接口,也就是交由InvocationHandler接口实现类MapperProxy里的invoke()方法去处理

    1 @SuppressWarnings("unchecked")
    2   protected T newInstance(MapperProxy<T> mapperProxy) {
    3     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    4   }
    5 
    6   public T newInstance(SqlSession sqlSession) {
    7     final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    8     return newInstance(mapperProxy);
    9   }

    然后就这样给UserMapper赋予了一个代理对象

    1 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    二、使用Mapper代理对象进行查询操作

    主代码调用代理对象查询,方法里面的参数为数据库字段的长整型id

    1 user = userMapper.getUser(30L);

    对应的mapper映射文件:

    1 <select id="getUser" parameterType="long" resultMap="userMap">
    2       SELECT user_id as id, user_name as username, sex, user_password as password, email from tb_user WHERE user_id = #{id}
    3 </select>

    使用Mapper代理对象,首先调用的是MapperProxy里面的invoke方法,传进三个主要的参数,分别是:代理对象、被调用的方法、方法的参数

     1 @Override
     2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     3     try {
     4       if (Object.class.equals(method.getDeclaringClass())) {
     5         return method.invoke(this, args);
     6       } else if (isDefaultMethod(method)) {
     7         return invokeDefaultMethod(proxy, method, args);
     8       }
     9     } catch (Throwable t) {
    10       throw ExceptionUtil.unwrapThrowable(t);
    11     }
    12     final MapperMethod mapperMethod = cachedMapperMethod(method);
    13     return mapperMethod.execute(sqlSession, args);
    14 }

    上面这段代码首先检查当前这个method是哪个类的方法,然后再判断有无默认方法,如果都没有则对方法进行缓存,最后对 SqlSession 进行的包装调用。

    MapperMethod对SqlSession的操作进行了封装,来看其中的一段execute方法源码

     1 public Object execute(SqlSession sqlSession, Object[] args) {
     2     Object result;
     3     switch (command.getType()) {
     4       case INSERT: {
     5         Object param = method.convertArgsToSqlCommandParam(args);
     6         result = rowCountResult(sqlSession.insert(command.getName(), param));
     7         break;
     8       }
     9       case UPDATE: {
    10         Object param = method.convertArgsToSqlCommandParam(args);
    11         result = rowCountResult(sqlSession.update(command.getName(), param));
    12         break;
    13       }
    14       case DELETE: {
    15         Object param = method.convertArgsToSqlCommandParam(args);
    16         result = rowCountResult(sqlSession.delete(command.getName(), param));
    17         break;
    18       }
    19       case SELECT:
    20         if (method.returnsVoid() && method.hasResultHandler()) {
    21           executeWithResultHandler(sqlSession, args);
    22           result = null;
    23         } else if (method.returnsMany()) {
    24           result = executeForMany(sqlSession, args);
    25         } else if (method.returnsMap()) {
    26           result = executeForMap(sqlSession, args);
    27         } else if (method.returnsCursor()) {
    28           result = executeForCursor(sqlSession, args);
    29         } else {
    30           Object param = method.convertArgsToSqlCommandParam(args);
    31           result = sqlSession.selectOne(command.getName(), param);
    32           if (method.returnsOptional() &&
    33               (result == null || !method.getReturnType().equals(result.getClass()))) {
    34             result = Optional.ofNullable(result);
    35           }
    36         }
    37         break;
    38       case FLUSH:
    39         result = sqlSession.flushStatements();
    40         break;
    41       default:
    42         throw new BindingException("Unknown execution method for: " + command.getName());
    43     }
    44     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    45       throw new BindingException("Mapper method '" + command.getName()
    46           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    47     }
    48     return result;
    49 }

    调用的mapper的查询操作,先看看上面这段的SELECT这一段代码。首先是看方法的返回值类型是否为空并且结果处理器resultHandler,有的话则执行实现的ResultHandler的方法;之后也是检查方法的参数和返回类型,有的话执行各种情况下的方法;都没有的话,把参数传进SQL命令中

    1 public Object convertArgsToSqlCommandParam(Object[] args) {
    2       return paramNameResolver.getNamedParams(args);
    3 }

    可以看到参数传递利用了ParamNameResolver,处理接口形式的参数,最后会把参数处放在一个map中,

     1 public Object getNamedParams(Object[] args) {
     2     final int paramCount = names.size();
     3     if (args == null || paramCount == 0) {
     4       return null;
     5     } else if (!hasParamAnnotation && paramCount == 1) {
     6       return args[names.firstKey()];
     7     } else {
     8       final Map<String, Object> param = new ParamMap<>();
     9       int i = 0;
    10       for (Map.Entry<Integer, String> entry : names.entrySet()) {
    11         param.put(entry.getValue(), args[entry.getKey()]);
    12         // add generic param names (param1, param2, ...)
    13         final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
    14         // ensure not to overwrite parameter named with @Param
    15         if (!names.containsValue(genericParamName)) {
    16           param.put(genericParamName, args[entry.getKey()]);
    17         }
    18         i++;
    19       }
    20       return param;
    21     }
    22  }

    参数解析完后,MapperMethod使用sqlSession,执行一条操作:

    1 result = sqlSession.selectOne(command.getName(), param);
     1 @Override
     2 public <T> T selectOne(String statement, Object parameter) {
     3     // Popular vote was to return null on 0 results and throw exception on too many.
     4     List<T> list = this.selectList(statement, parameter);
     5     if (list.size() == 1) {
     6       return list.get(0);
     7     } else if (list.size() > 1) {
     8       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
     9     } else {
    10       return null;
    11     }
    12 }
     1 @Override
     2 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     3     try {
     4       MappedStatement ms = configuration.getMappedStatement(statement);
     5       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
     6     } catch (Exception e) {
     7       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     8     } finally {
     9       ErrorContext.instance().reset();
    10     }
    11 }

    sqlSession的selectList最后使用到MappedStatement,这个MappedStatement是保存Mapper中一个SQL语句的结点。利用执行器进行查询,第二个参数是为了检查参数是不是一个集合;

     1 private Object wrapCollection(final Object object) {
     2     if (object instanceof Collection) {
     3       StrictMap<Object> map = new StrictMap<>();
     4       map.put("collection", object);
     5       if (object instanceof List) {
     6         map.put("list", object);
     7       }
     8       return map;
     9     } else if (object != null && object.getClass().isArray()) {
    10       StrictMap<Object> map = new StrictMap<>();
    11       map.put("array", object);
    12       return map;
    13     }
    14     return object;
    15 }

    第三个参数是rowBounds逻辑分页方式,这里使用的是默认的;第四个是执行器的参数,这里是null。

    然后跳到了CacheExecutor的query方法,它根据传进的MappedStatement参数获取BoundSql对象,ms中有mapper中的sql语句,放在SqlSource,然后根据传进来的参数组装成boundSql;之后生成一个对应二级缓存的key,

    1 @Override
    2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    3     BoundSql boundSql = ms.getBoundSql(parameterObject);
    4     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    5     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    6 }

    但是Mybatis默认只开启了一级缓存,本例中并没有开启二级缓存,所以直接执行最后一个父类delegate.query方法,

     1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     2       throws SQLException {
     3     Cache cache = ms.getCache();
     4     if (cache != null) {
     5       flushCacheIfRequired(ms);
     6       if (ms.isUseCache() && resultHandler == null) {
     7         ensureNoOutParams(ms, boundSql);
     8         @SuppressWarnings("unchecked")
     9         List<E> list = (List<E>) tcm.getObject(cache, key);
    10         if (list == null) {
    11           list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    12           tcm.putObject(cache, key, list); // issue #578 and #116
    13         }
    14         return list;
    15       }
    16     }
    17     return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    18 }

    上面调用的是BaseExecutor中的query方法,此方法中的最重要的一段代码

    1       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    2       if (list != null) {
    3         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    4       } else {
    5         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, 
    6         boundSql);
    7       }  

    因为我没有自己写的resultHandler类,所以直接执行

    1     list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

    其方法源码为BaseExecutor抽象类中的queryFromDataBase

     1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
     2     List<E> list;
     3     localCache.putObject(key, EXECUTION_PLACEHOLDER);
     4     try {
     5       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
     6     } finally {
     7       localCache.removeObject(key);
     8     }
     9     localCache.putObject(key, list);
    10     if (ms.getStatementType() == StatementType.CALLABLE) {
    11       localOutputParameterCache.putObject(key, parameter);
    12     }
    13     return list;
    14 }

    queryFromDataBase中在深入,主要是第5行的doQuery方法:

     1 @Override
     2 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
     3     Statement stmt = null;
     4     try {
     5       Configuration configuration = ms.getConfiguration();
     6       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     7       stmt = prepareStatement(handler, ms.getStatementLog());
     8       return handler.query(stmt, resultHandler);
     9     } finally {
    10       closeStatement(stmt);
    11     }
    12 }

    StatementHadler是四大核心对象之一,它的任务就是和数据库对话。上面这段代码configuration.newStatementHandler方法使用了RoutingStatementHandler(采用的适配器模式)创建StatementHandler:

     1 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
     2 
     3     switch (ms.getStatementType()) {
     4       case STATEMENT:
     5         delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
     6         break;
     7       case PREPARED:
     8         delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
     9         break;
    10       case CALLABLE:
    11         delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
    12         break;
    13       default:
    14         throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    15     }
    16 
    17 }

    RoutingStatementHandler执行query方法

    1 @Override
    2 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    3     return delegate.<E>query(statement, resultHandler);
    4 }

    PreparedStatementHandler执行query方法

    1 @Override
    2 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    3     PreparedStatement ps = (PreparedStatement) statement;
    4     ps.execute();
    5     return resultSetHandler.handleResultSets(ps);
    6  }

    DefaultResultSetHandler执行handleResultSets方法,getFirstResultSet获取第一个结果集在于知道sql语句要操作到哪些元素数据(表的列),会获取到元数据名称、Java数据类型、JDBC数据类型,之后getResultMaps获取执行的sql配置的resultMap,之后一个resultMap对应一个结果集,依次遍历resultMap并处理结果集

     1 @Override
     2 public List<Object> handleResultSets(Statement stmt) throws SQLException {
     3     ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
     4 
     5     final List<Object> multipleResults = new ArrayList<>();
     6 
     7     int resultSetCount = 0;
     8     ResultSetWrapper rsw = getFirstResultSet(stmt);
     9 
    10     List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    11     int resultMapCount = resultMaps.size();
    12     validateResultMapsCount(rsw, resultMapCount);
    13     while (rsw != null && resultMapCount > resultSetCount) { //一个resultMap对应一个结果集,依次遍历resultMap并处理结果集
    14       ResultMap resultMap = resultMaps.get(resultSetCount);
    15       handleResultSet(rsw, resultMap, multipleResults, null); // 处理结果集
    16       rsw = getNextResultSet(stmt);// 获取下一个结果集
    17       cleanUpAfterHandlingResultSet();
    18       resultSetCount++;
    19     }
    20 
    21     String[] resultSets = mappedStatement.getResultSets();
    22     if (resultSets != null) {
    23       while (rsw != null && resultSetCount < resultSets.length) {
    24         ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
    25         if (parentMapping != null) {
    26           String nestedResultMapId = parentMapping.getNestedResultMapId();
    27           ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
    28           handleResultSet(rsw, resultMap, null, parentMapping);
    29         }
    30         rsw = getNextResultSet(stmt);
    31         cleanUpAfterHandlingResultSet();
    32         resultSetCount++;
    33       }
    34     }
    35 
    36     return collapseSingleResultList(multipleResults); //把结果集转化为List
    37 }

    DefaultResultSetHandler的getFirstResultSet方法

     1 private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
     2     ResultSet rs = stmt.getResultSet();
     3     while (rs == null) { // 没有结果集,也许是数据库驱动还没有返回第一个结果集
     4       // move forward to get the first resultset in case the driver
     5       // doesn't return the resultset as the first result (HSQLDB 2.1)
     6       if (stmt.getMoreResults()) { // 尝试再一次获取结果集
     7         rs = stmt.getResultSet();
     8       } else {
     9         if (stmt.getUpdateCount() == -1) {  //表示驱动已经返回,没有更多结果,没有结果集
    10           // no more results. Must be no resultset
    11           break;
    12         }
    13       }
    14     }
    15     return rs != null ? new ResultSetWrapper(rs, configuration) : null; //不为空则返回结果集的包装
    16 }

    ResultSetWrapper构造函数(包装结果集)

     1 public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
     2     super();
     3     this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
     4     this.resultSet = rs;
     5     final ResultSetMetaData metaData = rs.getMetaData();
     6     final int columnCount = metaData.getColumnCount();
     7     for (int i = 1; i <= columnCount; i++) { // 设置结果集的元数据
     8       columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
     9       jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
    10       classNames.add(metaData.getColumnClassName(i));
    11     }
    12 }

    把结果集转化为List

    1 @SuppressWarnings("unchecked")
    2 private List<Object> collapseSingleResultList(List<Object> multipleResults) {
    3     return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
    4 }

    然后一层层传递回去,最后获得查询结果。(待续)

  • 相关阅读:
    linux的vim按了ctrl+s之后假死的解决办法
    linux下的终端模拟器urxvt的配置
    vim下正则表达式的非贪婪匹配
    linux中的一个看图的软件
    解决windows的控制台显示utf8乱码的问题
    [PHP][位转换积累]之异或运算的简单加密应用
    [PHP][REDIS]phpredis 'RedisException' with message 'read error on connection'
    [PHP][位转换积累]之与运算截取二进制流的值
    [PHP][位转换积累]之pack和unpack
    [正则表达式]PCRE反向分组引用
  • 原文地址:https://www.cnblogs.com/magic-sea/p/11204286.html
Copyright © 2020-2023  润新知