MyBatis源码分析:SQL执行过程分析
Posted magic-sea
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
然后一层层传递回去,最后获得查询结果。(待续)
以上是关于MyBatis源码分析:SQL执行过程分析的主要内容,如果未能解决你的问题,请参考以下文章