Mybatis源码(八):查询执行流程
在Mybatis源码(七):SQL执行流程中已提到,根据不同的sqlCommandType执行不同类型的SQL,下面来看看调用Mapper接口的查询,Mybatis中做了哪些处理。
UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 查询流程 User user02 = mapper.selectUserById("101");
查询流程最终会执行到DefaultSqlSession的selectList()方法,DefaultSqlSession#selectList() 核心伪代码
1 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 2 // 根据statement id找到对应的MappedStatement 3 MappedStatement ms = configuration.getMappedStatement(statement); 4 // 如果 cacheEnabled = true(默认),Executor会被 CachingExecutor装饰 5 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); 6 }
1、获取MappedStatement对象
根据statementid(sql标识id)从全局配置对象configuration中拿到MappedStatement,MappedStatement对象中有Mapper.xml中标签的所有属性,如id、statementType、sqlSource、useCache、入参、出参等等。
2、SQL查询执行流程
Executor是在创建sqlSession对象时创建的,在创建BaseExecutor/ReuseExecutor/BatchExecutor之后,根据缓存配置判断是否对Executor进行对象装饰。
1 // 默认启用缓存 2 protected boolean cacheEnabled = true; 3 4 if (cacheEnabled) { 5 executor = new CachingExecutor(executor); 6 }
若显示的在settings中配置cacheEnabled=false,会直接调用BaseExecutor的query方法。若未配置或者显示的在settings中配置cacheEnabled=true,会优先执行CachingExecutor中的逻辑,之后再调用BaseExecutor中的逻辑。
CachingExecutor#query() 核心代码:
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 2 // 创建BoundSql对象,包含目标执行sql、参数信息 3 BoundSql boundSql = ms.getBoundSql(parameterObject); 4 // 创建CacheKey对象,用来命中缓存 5 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); 6 // 查询 7 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 8 }
1、创建BoundSql对象
StaticSqlSource#getBoundSql() 核心代码
public BoundSql getBoundSql(Object parameterObject) { return new BoundSql(configuration, sql, parameterMappings, parameterObject); }
BoundSql对象中包含全局配置对象、xml中的sql,xml中sql的占位符#{},sql参数。
2、创建CacheKey对象
若全局配置中开启了缓存,会根据CacheKey来判断要执行的SQL是否已经被缓存,怎样判断相同的SQL呢?下面来看CaacheKey对象
创建缓存key,BaseExecutor#createCacheKey() 核心方法:
1 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { 2 // 检测当前executor是否已经关闭 3 if (closed) { 4 throw new ExecutorException("Executor was closed."); 5 } 6 // 创建CacheKey对象 7 CacheKey cacheKey = new CacheKey(); 8 // 查询的目标执行方法全路径限定名,并添加到cacheKey对象中 9 cacheKey.update(ms.getId()); 10 // 分页起始位置,并添加到cacheKey对象中 11 cacheKey.update(rowBounds.getOffset()); 12 // 每页记录数,并添加到cacheKey对象中 13 cacheKey.update(rowBounds.getLimit()); 14 // 目标执行sql添加到cacheKey对象中 15 cacheKey.update(boundSql.getSql()); 16 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); // sql中的变量参数映射 17 TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // 获取类型处理注册器 18 // mimic DefaultParameterHandler logic 19 // 获取用户传入的实参,并添加到CacheKey对象中 20 for (ParameterMapping parameterMapping : parameterMappings) { 21 if (parameterMapping.getMode() != ParameterMode.OUT) { 22 Object value; 23 String propertyName = parameterMapping.getProperty(); // 获取xml文件中SQL中的变量#{?} 24 if (boundSql.hasAdditionalParameter(propertyName)) { 25 value = boundSql.getAdditionalParameter(propertyName); 26 } else if (parameterObject == null) { 27 value = null; 28 } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { // 用户传入实参是否有类型处理器 29 value = parameterObject; 30 } else { 31 MetaObject metaObject = configuration.newMetaObject(parameterObject); 32 value = metaObject.getValue(propertyName); 33 } 34 // 将实参添加到CacheKey对象中 35 cacheKey.update(value); 36 } 37 } 38 // 如果environment的id不为空,则将其添加到CacheKey中 39 if (configuration.getEnvironment() != null) { 40 // issue #176 41 cacheKey.update(configuration.getEnvironment().getId()); 42 } 43 return cacheKey; 44 }
Mybatis中,拥有相同的方法、翻页偏移、SQL、参数值、数据源环境会被认为是同一个SQL查询。
1 // 创建CacheKey对象 2 CacheKey cacheKey = new CacheKey(); 3 // 查询的目标执行方法全路径限定名,并添加到cacheKey对象中 4 cacheKey.update(ms.getId()); 5 // 分页起始位置,并添加到cacheKey对象中 6 cacheKey.update(rowBounds.getOffset()); 7 // 每页记录数,并添加到cacheKey对象中 8 cacheKey.update(rowBounds.getLimit()); 9 // 目标执行sql添加到cacheKey对象中 10 cacheKey.update(boundSql.getSql()); 11 // 将实参添加到CacheKey对象中 12 cacheKey.update(value); 13 // environment环境信息 14 cacheKey.update(configuration.getEnvironment().getId());
CacheKey#update() 核心代码:
1 private static final int DEFAULT_MULTIPLYER = 37; 2 private static final int DEFAULT_HASHCODE = 17; 3 4 // 用于判重 5 private int hashcode; 6 private long checksum; 7 private int count; 8 // 存储sql信息,用于生成hashcode 9 private List<Object> updateList; 10 11 // 构造函数 12 public CacheKey() { 13 this.hashcode = DEFAULT_HASHCODE; 14 this.multiplier = DEFAULT_MULTIPLYER; 15 this.count = 0; 16 this.updateList = new ArrayList<>(); 17 } 18 19 // CacheKey设置进updateList属性中 20 public void update(Object object) { 21 int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 22 23 count++; 24 checksum += baseHashCode; 25 baseHashCode *= count; 26 27 // 每添加一个object,都重新生成hashCode 28 // 生成hashCode用于快速判重,判断是否为同一个CacheKey,重写了Object的equals方法 29 hashcode = multiplier * hashcode + baseHashCode; 30 31 // 将值设置进updateList中 32 updateList.add(object); 33 }
hashCode()是Object中的一个本地方法,通过随机数算法生成,返回自己生成的hashCode。
在生成CacheKey的时候(update方法),每添加一个参数就会重新生成CacheKey的hashCode,hashcode是用乘法哈希生成的(基数baseHashCode=17,乘法因子multiplier=37)。
CacheKey对hashCode()方法进行了重写,若哈希值(乘法哈希)、校验值(加法哈希)、要素个数任何一个不相等,都不是同一个查询,最后循环比较要素,防止哈希碰撞。
1 // 重写equals方法 2 public boolean equals(Object object) { 3 if (this == object) { 4 return true; 5 } 6 if (!(object instanceof CacheKey)) { 7 return false; 8 } 9 10 final CacheKey cacheKey = (CacheKey) object; 11 12 if (hashcode != cacheKey.hashcode) { 13 return false; 14 } 15 if (checksum != cacheKey.checksum) { 16 return false; 17 } 18 if (count != cacheKey.count) { 19 return false; 20 } 21 22 for (int i = 0; i < updateList.size(); i++) { 23 Object thisObject = updateList.get(i); 24 Object thatObject = cacheKey.updateList.get(i); 25 if (!ArrayUtil.equals(thisObject, thatObject)) { 26 return false; 27 } 28 } 29 return true; 30 }
CacheKey详情如下:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713035-733371293.png)
3、执行查询SQL
CacheKey生成之后,执行SQL查询,CachingExecutor#query() 核心代码:
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2 throws SQLException { 3 // 获取查询语句所在命名空间对应的二级缓存 4 Cache cache = ms.getCache(); 5 // 是否开启了二级缓存 6 if (cache != null) { 7 // 根据select节点的配置,决定是否需要清空二级缓存 8 flushCacheIfRequired(ms); 9 // 检测SQL节点的useCache配置以及是否使用了resultHandler配置 10 if (ms.isUseCache() && resultHandler == null) { 11 // 二级缓存不能保存输出类型的参数,如果查询操作调用了包含输出参数的存储过程,则报错 12 ensureNoOutParams(ms, boundSql); 13 @SuppressWarnings("unchecked") 14 // 查询二级缓存 15 List<E> list = (List<E>) tcm.getObject(cache, key); 16 if (list == null) { 17 // 二级缓存没有相应的结果对,调用封装的Executor对象的query方法 18 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 19 // 将查询结果保存到TransactionalCache.entriesToAddOnCommit集合中 20 tcm.putObject(cache, key, list); // issue #578 and #116 21 } 22 return list; 23 } 24 } 25 // 没有启动二级缓存,直接调用底层Executor执行数据库查询操作 26 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 27 }
优先判断缓存开关是否打开,若缓存开关打开,根据CacheKey判断是否命中二级缓存,未命中二级缓存,调用CachingExecutor持有的Executor对象的query方法。
调用CachingExecutor父类BaseExecutor的query()方法,BaseExecutor#query() 核心伪代码
1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 3 // 检测当前Executor是否已经关闭 4 if (closed) { 5 throw new ExecutorException("Executor was closed."); 6 } 7 // 非嵌套查询,并且select节点配置的flushCache属性为true时,才会清空一级缓存 8 if (queryStack == 0 && ms.isFlushCacheRequired()) { 9 clearLocalCache(); 10 } 11 List<E> list; 12 try { 13 // 增加查询层数 14 queryStack++; 15 // 查询一级缓存 16 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 17 // 针对存储过程调用的处理,在一级缓存命中时,获取缓存中保存的输出类型参数,并设置到用户传入的实参对象中 18 if (list != null) { 19 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 20 // 调用doQuery方法完成数据库查询,并得到映射后的结果对象 21 } else { 22 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 23 } 24 } finally { 25 // 当前查询完成,查询层数减少 26 queryStack--; 27 } 28 // .. 29 return list; 30 }
根据CacheKey判断是否命中一级缓存,若命中一级缓存则从缓存中获取;若未命中一级缓存,执行queryFromDatabase()方法查询数据库。
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 // 在一级缓存中添加占位符 4 localCache.putObject(key, EXECUTION_PLACEHOLDER); 5 try { 6 // 完成数据库查询操作,并返回结果对象,默认Simple 7 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 8 } finally { 9 // 删除缓存占位符 10 localCache.removeObject(key); 11 } 12 // 将真正的结果对象添加到一级缓存中 13 localCache.putObject(key, list); 14 // 存储过程的调用处理 15 if (ms.getStatementType() == StatementType.CALLABLE) { 16 // 缓存输出类型的参数 17 localOutputParameterCache.putObject(key, parameter); 18 } 19 return list; 20 }
SimpleExecutor#doQuery() 核心代码
1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 2 Statement stmt = null; 3 try { 4 // 获取全局配置对象configuration 5 Configuration configuration = ms.getConfiguration(); 6 // 创建StatementHandler对象,实际返回的是RoutingStatementHandler对象 7 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 8 // 完成SQL中变量值的替换,获取完整的sql查询语句 9 stmt = prepareStatement(handler, ms.getStatementLog()); 10 return handler.query(stmt, resultHandler); 11 } finally { 12 closeStatement(stmt); 13 } 14 }
3.1、StatementHandler
configuration.newStatementHandler()创建一个RoutingStatementHandler对象,并向StatementHandler中植入插件。
1 //创建语句处理器 2 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 3 //创建路由选择语句处理器,根据statementType创建不同类型的StatementHandler 4 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); 5 // 植入插件 6 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); 7 return statementHandler; 8 }
RoutingStatementHandler构造函数详情如下:
1 // 封装的真正的StatementHandler对象 2 private final StatementHandler delegate; 3 4 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 5 // 根据MappedStatement的配置,生成一个对应的StatementHandler对象,并设置到delegate字段中 6 switch (ms.getStatementType()) { 7 case STATEMENT: 8 delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); 9 break; 10 case PREPARED: 11 delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); 12 break; 13 case CALLABLE: 14 delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); 15 break; 16 default: 17 throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); 18 } 19 }
根据同的MappedStatement中的statementType属性,判断要创建何种类型的StatementHandler,默认为PREPARED。statementType设值的位置,详见Mybatis 源码(四):Mapper的解析工作。
StatementType详情如下:
1 public enum StatementType { 2 // STATEMENT 对应 SimpleStatementHandler 3 // PREPARED 对应 PreparedStatementHandler 4 // CALLABLE 对应 CallableStatementHandler 5 STATEMENT, PREPARED, CALLABLE 6 }
在创建PreparedStatementHandler对象时,调用父类BaseStatementHandler的构造方法,初始化了参数处理器parameterHandler,返回结果处理器resultSetHandler。
1 // 将结果集映射成结果对象 2 protected final ResultSetHandler resultSetHandler; 3 // 参数处理器,SQL语句占位符设置为实参 4 protected final ParameterHandler parameterHandler; 5 6 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { 7 this.configuration = mappedStatement.getConfiguration(); 8 this.executor = executor; 9 this.mappedStatement = mappedStatement; 10 this.rowBounds = rowBounds; 11 12 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 13 this.objectFactory = configuration.getObjectFactory(); 14 15 if (boundSql == null) { // issue #435, get the key before calculating the statement 16 generateKeys(parameterObject); 17 boundSql = mappedStatement.getBoundSql(parameterObject); 18 } 19 20 this.boundSql = boundSql; 21 22 // 创建parameterHandler并完成赋值 23 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); 24 // 创建resultSetHandler并完成赋值 25 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); 26 }
参数处理器parameterHandler,主要为SQL语句绑定实参,用传入的实参替换SQL语句中的占位符?
返回值处理器resultSetHandler,主要将结果集映射为结果对象。
1、参数处理器
Mybatis中参数处理器默认为DefaultParameterHandler,在创建参数处理器后,可植入插件做拦截处理。configuration#newParameterHandler() 核心代码
1 // 创建参数处理器 2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { 3 // 创建ParameterHandler 4 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); 5 // 设置插件 6 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); 7 return parameterHandler; 8 }
DefaultParameterHandler详情如下:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131712939-32790885.png)
2、返回值处理器
Mybatis中结果集处理器默认为DefaultResultSetHandler,在设置结果集处理器时,可植入插件做拦截处理。1 // 创建结果集处理器 2 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, 3 ResultHandler resultHandler, BoundSql boundSql) { 4 //创建DefaultResultSetHandler 5 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); 6 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 7 return resultSetHandler; 8 }
DefaultResultSetHandler详情如下:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713001-210590718.png)
3.2、Statement -> SQL语句处理
SQL语句处理,主要完成三件事:获取数据库连接、创建数据库执行对象Statement、使用Statement对象替换SQL中的占位符?。
SimpleExecutor#prepareStatement() 核心代码:
1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 2 Statement stmt; 3 // 获取JBDC连接 4 Connection connection = getConnection(statementLog); 5 // 创建Statement对象 6 stmt = handler.prepare(connection, transaction.getTimeout()); 7 // 处理SQL中的占位符1 8 handler.parameterize(stmt); 9 return stmt; 10 }
1、获取数据库连接
BaseExecutor#getConnection() 核心代码:
1 // Transaction对象,实现事务的提交、回滚和关闭操作 2 protected Transaction transaction; 3 4 protected Connection getConnection(Log statementLog) throws SQLException { 5 Connection connection = transaction.getConnection(); 6 if (statementLog.isDebugEnabled()) { 7 // 若需要打印日志,返回一个ConnectionLogger(代理模式, AOP思想) 8 return ConnectionLogger.newInstance(connection, statementLog, queryStack); 9 } else { 10 // 不需要打印日志,直接返回连接 11 return connection; 12 } 13 }
在全局配置中事务管理器设置的为JDBC。
<transactionManager type="JDBC"/>
在类型初始化configuration时,别名注册器中对JDBC别名映射的对象为JdbcTransactionFactory,通过事务工厂创建事务对象JdbcTransaction。
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
从事务中获取连接,JdbcTransaction#openConnection() 核心代码:
1 protected void openConnection() throws SQLException { 2 connection = dataSource.getConnection(); 3 if (level != null) { 4 connection.setTransactionIsolation(level.getLevel()); 5 } 6 setDesiredAutoCommit(autoCommit); 7 }
从数据源中获取连接,并设置连接的自动提交标识,默认为false; 若指定了事务隔离级别,则设置连接的隔离级别。
2、创建Statement对象
BaseStatementHandler#prepare() 核心伪代码
1 public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { 2 ErrorContext.instance().sql(boundSql.getSql()); 3 Statement statement = null; 4 // 实例化Statement 5 statement = instantiateStatement(connection); 6 // 设置超时 7 setStatementTimeout(statement, transactionTimeout); 8 // 设置读取条数 9 setFetchSize(statement); 10 return statement; 11 }
1、实例化Statement对象
实例化Statement对象,PreparedStatementHandler#instantiateStatement() 核心代码
1 protected Statement instantiateStatement(Connection connection) throws SQLException { 2 // 获取待执行的SQL语句 3 String sql = boundSql.getSql(); 4 // 根据keyGenerator字段的值,创建PreparedStatement对象 5 if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { 6 String[] keyColumnNames = mappedStatement.getKeyColumns(); 7 if (keyColumnNames == null) { 8 // 返回数据库生成的主键 9 return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); 10 } else { 11 // 在insert语句执行完成之后,会将keyColumnNames指定的列返回 12 return connection.prepareStatement(sql, keyColumnNames); 13 } 14 } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { 15 // 创建普通的PreparedStatement对象 16 return connection.prepareStatement(sql); 17 } else { 18 // 设置结果集是否可以滚动以及其游标是否可以上下移动,设置结果集是否可更新 19 return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); 20 } 21 }
在解析Mapper配置,MapperBuilderAssistant#addMappedStatement()中创建MappedStatement的内部类Builder时,会将MappedStatement中resultSetType属性设置为ResultSetType.DEFAULT。
根据及resultSetType类型,通过数据库连接创建PreparedStatement对象。
2、PreparedStatement属性填充
设置PreparedStatement的超时时间及匹配数据条数。
3、SQL语句占位符处理
DefaultParameterHandler#setParameters() 核心伪代码:
1 // 用户传入的实参对象 2 private final Object parameterObject; 3 4 public void setParameters(PreparedStatement ps) { 5 ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); 6 // 取出sql中的参数映射列表 7 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 8 // 检测parameterMappings集合是否为空 9 if (parameterMappings != null) { 10 for (int i = 0; i < parameterMappings.size(); i++) { 11 ParameterMapping parameterMapping = parameterMappings.get(i); 12 // 过滤掉存储过程中的输出参数 13 if (parameterMapping.getMode() != ParameterMode.OUT) { 14 // 记录绑定的实参 15 Object value; 16 // 获取参数名称 17 String propertyName = parameterMapping.getProperty(); 18 // ... 19 if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { 20 // 实参可以直接通过TypeHandler转换成JdbcType 21 value = parameterObject; 22 } 23 // ... 24 // 获取parameterMapping中设置的TypeHandler对象 25 TypeHandler typeHandler = parameterMapping.getTypeHandler(); 26 JdbcType jdbcType = parameterMapping.getJdbcType(); 27 if (value == null && jdbcType == null) { 28 //不同类型的set方法不同,所以委派给子类的setParameter方法 29 jdbcType = configuration.getJdbcTypeForNull(); 30 } 31 // 调用PreparedStatement.set*方法为SQL语句绑定相应的实参 32 typeHandler.setParameter(ps, i + 1, value, jdbcType); 33 } 34 } 35 } 36 }
获取sql中的参数映射表,包含了参数值Mapper.xml中映射SQL中的参数名称#{id},及参数类型。
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713008-2118118175.png)
遍历参数映射表,类型处理器注册器中是否有处理器可以处理用户传入的实参类型,TypeHandlerRegistry#hasTypeHandler() 核心代码:
1 // 判断类型处理注册器中的类型处理器是否可以处理javaType、jdbcType 2 public boolean hasTypeHandler(Class<?> javaType, JdbcType jdbcType) { 3 return javaType != null && getTypeHandler((Type) javaType, jdbcType) != null; 4 }
TypeHandlerRegistry#getTypeHandler() 核心代码:
1 private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) { 2 // 查找或初始化Java类型对应的TypeHandler集合 3 Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type); 4 TypeHandler<?> handler = null; 5 if (jdbcHandlerMap != null) { 6 // 根据JdbcType类型查找TypeHandler对象 7 handler = jdbcHandlerMap.get(jdbcType); 8 if (handler == null) { 9 handler = jdbcHandlerMap.get(null); 10 } 11 if (handler == null) { 12 // 如果jdbcHandlerMap只注册了一个TypeHandler,则使用此TypeHandler对象 13 handler = pickSoleHandler(jdbcHandlerMap); 14 } 15 } 16 // type drives generics here 17 return (TypeHandler<T>) handler; 18 }
根据用户传入参数的Class、JdbcType获取缓存中的TypeHandler。Mybatis中所有的类型处理器都在TypeHandlerRegistry的TYPE_HANDLER_MAP属性中。
TypeHandlerRegistry#getJdbcHandlerMap 核心伪代码:
1 // 记录了Java类型向指定JdbcType转换时,需要使用TypeHandler对象 2 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>(); 3 // 空TypeHandler集合 4 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); 5 6 private Map<JdbcType, TypeHandler<?>> getJdbcHandlerMap(Type type) { 7 // 查找指定Java类型对应TypeHandler集合 8 Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type); 9 // 检测是否为空集合标识 10 if (NULL_TYPE_HANDLER_MAP.equals(jdbcHandlerMap)) { 11 return null; 12 } 13 // ... 14 TYPE_HANDLER_MAP.put(type, jdbcHandlerMap == null ? NULL_TYPE_HANDLER_MAP : jdbcHandlerMap); 15 return jdbcHandlerMap; 16 }
getJdbcHandlerMap()方法,根据传入实参Class,从TypeHandlerRegistry中TYPE_HANDLER_MAP属性中获取对应的类型处理器,若未匹配到,并且TYPE_HANDLER_MAP为空集合,返回null;若根据Class类型未匹配到TYPE_HANDLER_MAP中的类型处理器,将Class类型对应的类型处理器设置为空集合,返回获取到的类型处理器集合,最后再根据jdbcType的类型锁定目标参数类型处理器。
在TypeHandlerRegistry的构造函数中,完成了常用Class类型处理器的注册。
类型处理器集合示例如下:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713022-973649149.png)
最后调用类型处理器基类BaseTypeHandler中的setParameter,BaseTypeHandler#setParameter() 核心伪代码:
1 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { 2 if (parameter == null) { 3 if (jdbcType == null) { 4 throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters."); 5 } 6 ps.setNull(i, jdbcType.TYPE_CODE); 7 } else { 8 setNonNullParameter(ps, i, parameter, jdbcType); 9 } 10 }
当用户传入实参不为空时,调用子类的类型处理器setNonNullParameter方法,不同的类型处理器会设置不同的参数类型。如String类型处理器,StringTypeHandler#setNonNullParameter() 核心代码:
1 public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) 2 throws SQLException { 3 ps.setString(i, parameter); 4 }
获取到的Statement对象详情如下,已生成可执行的SQL语句。
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713012-471141126.png)
3.3、执行SQL查询
获取到PreparedStatement对象,执行SQL,处理结果集,PreparedStatementHandler#query() 核心代码:
1 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { 2 PreparedStatement ps = (PreparedStatement) statement; 3 // JDBC的执行SQL查询 4 ps.execute(); 5 // 处理结果集 6 return resultSetHandler.handleResultSets(ps); 7 }
DefaultResultSetHandler#handleResultSets() 核心代码:
1 public List<Object> handleResultSets(Statement stmt) throws SQLException { 2 ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); 3 4 // 该集合用于保存映射结果得到的结果对象 5 final List<Object> multipleResults = new ArrayList<>(); 6 7 int resultSetCount = 0; 8 // 创建ResultSetWrapper对象,封装resultSet 9 ResultSetWrapper rsw = getFirstResultSet(stmt); 10 11 // 获取MappedStatement.resultMaps集合 12 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); 13 int resultMapCount = resultMaps.size(); 14 15 // 遍历resultMaps集合 16 while (rsw != null && resultMapCount > resultSetCount) { 17 // 获取该结果集对应的ResultMap对象 18 ResultMap resultMap = resultMaps.get(resultSetCount); 19 // 根据ResultMap中定义的映射规则对ResultSet进行映射,并将映射的结果对象添加到multipleResult集合中保存 20 handleResultSet(rsw, resultMap, multipleResults, null); 21 // 获取下一个结果集 22 rsw = getNextResultSet(stmt); 23 // 清空nestedResultObjects集合 24 cleanUpAfterHandlingResultSet(); 25 // 递增resultSetCount 26 resultSetCount++; 27 } 28 // ... 29 30 return collapseSingleResultList(multipleResults); 31 }
1、创建ResultSetWrapper对象封装ResultSet
ResultSetWrapper持有ResultSet,类型处理器注册器,包含表字段、表字段类型及表字段对应的Java类型,ResultSetWrapper详情如下:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713037-1237819114.png)
2、获取MappedStatement.resultMaps集合
MappedStatement.resultMaps是在解析Mapper.xml的select|insert|update|delete标签,创建MappedStatement.Builder对象时,完成的初始化操作。
初始化resultMaps的链路:XMLMapperBuilder#configurationElement() -> XMLMapperBuilder#buildStatementFromContext() -> XMLStatementBuilder#parseStatementNode() -> MapperBuilderAssistant#addMappedStatement()
resultMaps持有结果映射的类型type,statementId -> 目标执行方法全限定名,resultMaps示例详情:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713021-1006421004.png)
3、结果集映射成结果对象集合
遍历resultMaps集合,逐个处理resultMap,DefaultResultSetHandler#handleResultSet() 核心伪代码:
1 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException { 2 try { 3 // ... 4 // 用户未指定处理映射结果对象的ResultHandler对象,则使用DefaultResultHandler作为默认的ResultHandler对象 5 if (resultHandler == null) { 6 // 默认的结果处理器 7 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); 8 // 对ResultSet进行映射,并将映射得到的结果对象添加到DefaultResultHandler对象中暂存 9 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); 10 // 将DefaultResultHandler中保存的结果对象添加到multipleResults集合中 11 multipleResults.add(defaultResultHandler.getResultList()); 12 13 // 使用用户指定的ResultHandler对象处理结果对象 14 } else { 15 handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); 16 } 17 } 18 } finally { 19 // 调用ResultSet.close方法关闭结果集 20 closeResultSet(rsw.getResultSet()); 21 } 22 }
ResultSet结果集的处理,可以自定义resultHandler结果集处理器;若未指定resultHandler,使用默认的结果集处理器DefaultResultHandler。默认结果集处理器DefaultResultHandler通过ObjectFactory对象实例化List属性,DefaultResultHandler详情:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131712962-239929627.png)
对结果集进行映射,会将映射的对象设置在defaultResultHandler对象的List属性中。
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
1、创建结果映射对象并完成对象属性填充,将填充完成的对象存储在DefaultResultSetHandler的List属性中
最终会执行到 DefaultResultSetHandler的handleRowValuesForSimpleResultMap()方法
1 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) 2 throws SQLException { 3 // 默认上下文对象 4 DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); 5 ResultSet resultSet = rsw.getResultSet(); 6 // 根据RowBounds中的offset定位到指定的记录 7 skipRows(resultSet, rowBounds); 8 // 检测已经处理的行数是否已经达到上限(RowBounds,limit)以及ResultSet中是否还有可处理的记录 9 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { 10 // 根据该行记录以及ResultMap.discriminator,决定映射使用的ResultMap 11 ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); 12 // 根据最终确定的ResultMap对ResultSet中的该行记录进行映射,得到映射后的结果对象 13 Object rowValue = getRowValue(rsw, discriminatedResultMap, null); 14 // 将映射创建的结果对象添加到ResultHandler.resultList中保存 15 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); 16 } 17 }
将结果集映射成结果对象 DefaultResultSetHandler#getRowValue() 核心代码:
1 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { 2 // 实例化ResultLoaderMap(延迟加载器) 3 final ResultLoaderMap lazyLoader = new ResultLoaderMap(); 4 // 创建该行记录映射之后得到的结果对象,该结果对象的类型由ResultMap节点的type属性指定 5 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); 6 if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { 7 // 创建上述结果对象相应的MetaObject对象 8 final MetaObject metaObject = configuration.newMetaObject(rowValue); 9 // 成功映射任意属性,则foundValues为true,否则foundValues为false 10 boolean foundValues = this.useConstructorMappings; 11 // 检测是否需要进行自动映射 12 if (shouldApplyAutomaticMappings(resultMap, false)) { 13 // 自动映射ResultMap中未明确指定的列 14 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; 15 } 16 // 映射ResultMap中明确指定需要映射的列 17 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; 18 foundValues = lazyLoader.size() > 0 || foundValues; 19 // 如果没有成功映射任何属性,则根据mybatis-config.xml中的returnInstanceForEmptyRow配置决定返回空的结果对象还是返回null 20 rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; 21 } 22 return rowValue; 23 }
1、实例化空的映射结果对象
createResultObject() 根据resultMap中的type实例化对象,rowValue详情如下
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131712984-627842740.png)
2、映射结果属性填充
DefaultResultSetHandler#applyAutomaticMappings() 核心代码
1 // 自动映射 2 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { 3 // 获取ResultSet中存在,但ResultMap中没有明确映射的列所对应的UnMappedColumnAutoMapping集合,如果ResultMap中设置的resultType为HashMap的话,则全部的列都会在这里获取到 4 List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); 5 boolean foundValues = false; 6 if (!autoMapping.isEmpty()) { 7 // 遍历autoMapping集合 8 for (UnMappedColumnAutoMapping mapping : autoMapping) { 9 // 使用TypeHandler获取自动映射的列值 10 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); 11 if (value != null) { 12 foundValues = true; 13 } 14 if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { 15 // 将自动映射的属性值设置到结果对象中 16 metaObject.setValue(mapping.property, value); 17 } 18 } 19 } 20 return foundValues; 21 }
获取autoMapping,即获取数据字段与Java实体对象的映射关系及类型处理器,autoMapping详情如下:
![截图.png](https://img2023.cnblogs.com/blog/1680081/202303/1680081-20230321131713114-820544730.png)
遍历autoMapping集合,通过类型处理器获取自动映射的列值,根据映射结果对象的元数据对象metaObject,完成对象属性的设置,本质上是通过反射完成赋值操作的。
3、映射结果对象信息存储
映射结果对象信息最后会添加进DefaultResultHandler的List属性中进行存储,遍历下一个ResultSet结果集继续上述流程处理。
DefaultResultSetHandler#storeObject() 核心伪代码
1 private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException { 2 // 普通映射,将结果对象保存到ResultHandler中 3 callResultHandler(resultHandler, resultContext, rowValue); 4 }
DefaultResultSetHandler#callResultHandler() 核心伪代码:
1 private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) { 2 //将结果对象保存到 DefaultResultContext.resultObject字段中 3 resultContext.nextResultObject(rowValue); 4 // 将结果对象添加到ResultHandler.resultList中保存 5 ((ResultHandler<Object>) resultHandler).handleResult(resultContext); 6 }
DefaultResultHandler#handleResult() 核心代码:
public void handleResult(ResultContext<? extends Object> context) { list.add(context.getResultObject()); }
2、将暂存在DefaultResultSetHandler#List中映射结果对象集合赋值给multipleResults集合
3、关闭ResultSet
3.4、返回映射对象结果集合
3.5、关闭Statement
至此,完成了SQL查询的分析。