MyBatis框架及原理分析
MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架,其主要就完成 2 件事情:
- 封装 JDBC 操作
- 利用反射打通 Java 类与 SQL 语句之间的相互转换
MyBatis 的主要设计目的就是让我们对执行 SQL 语句时对输入输出的数据管理更加方便,所以方便地写出 SQL 和方便地获取 SQL 的执行结果才是 MyBatis 的核心竞争力。
MyBatis 的配置
MyBatis 框架和其他绝大部分框架一样,需要一个配置文件,其配置文件大致如下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="false"/>
<!--<setting name="logImpl" value="STDOUT_LOGGING"/> <!– 打印日志信息 –>-->
</settings><span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">typeAliases</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">typeAlias </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="com.luo.dao.UserDao"</span><span style="color: rgba(255, 0, 0, 1)"> alias</span><span style="color: rgba(0, 0, 255, 1)">="User"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">typeAliases</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">environments </span><span style="color: rgba(255, 0, 0, 1)">default</span><span style="color: rgba(0, 0, 255, 1)">="development"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">environment </span><span style="color: rgba(255, 0, 0, 1)">id</span><span style="color: rgba(0, 0, 255, 1)">="development"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">transactionManager </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="JDBC"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 128, 0, 1)"><!--</span><span style="color: rgba(0, 128, 0, 1)">事务管理类型</span><span style="color: rgba(0, 128, 0, 1)">--></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">dataSource </span><span style="color: rgba(255, 0, 0, 1)">type</span><span style="color: rgba(0, 0, 255, 1)">="POOLED"</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="username"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="luoxn28"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="password"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="123456"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="driver"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="com.mysql.jdbc.Driver"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">property </span><span style="color: rgba(255, 0, 0, 1)">name</span><span style="color: rgba(0, 0, 255, 1)">="url"</span><span style="color: rgba(255, 0, 0, 1)"> value</span><span style="color: rgba(0, 0, 255, 1)">="jdbc:mysql://192.168.1.150/ssh_study"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">dataSource</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">environment</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">environments</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">mappers</span><span style="color: rgba(0, 0, 255, 1)">></span> <span style="color: rgba(0, 0, 255, 1)"><</span><span style="color: rgba(128, 0, 0, 1)">mapper </span><span style="color: rgba(255, 0, 0, 1)">resource</span><span style="color: rgba(0, 0, 255, 1)">="userMapper.xml"</span><span style="color: rgba(0, 0, 255, 1)">/></span> <span style="color: rgba(0, 0, 255, 1)"></</span><span style="color: rgba(128, 0, 0, 1)">mappers</span><span style="color: rgba(0, 0, 255, 1)">></span>
</configuration>
以上配置中,最重要的是数据库参数的配置,比如用户名密码等,如果配置了数据表对应的 mapper 文件,则需要将其加入到 <mappers> 节点下。
MyBatis 的主要成员
- Configuration MyBatis 所有的配置信息都保存在 Configuration 对象之中,配置文件中的大部分配置都会存储到该类中
- SqlSession 作为 MyBatis 工作的主要顶层 API,表示和数据库交互时的会话,完成必要数据库增删改查功能
- Executor MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护
- StatementHandler 封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参数等
- ParameterHandler 负责对用户传递的参数转换成 JDBC Statement 所对应的数据类型
- ResultSetHandler 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合
- TypeHandler 负责 java 数据类型和 jdbc 数据类型 (也可以说是数据表列类型) 之间的映射和转换
- MappedStatement MappedStatement 维护一条 <select|update|delete|insert> 节点的封装
- SqlSource 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回
- BoundSql 表示动态生成的 SQL 语句以及相应的参数信息
以上主要成员在一次数据库操作中基本都会涉及,在 SQL 操作中重点需要关注的是 SQL 参数什么时候被设置和结果集怎么转换为 JavaBean 对象的,这两个过程正好对应 StatementHandler 和 ResultSetHandler 类中的处理逻辑。
( 图片来自《深入理解 mybatis 原理》 MyBatis 的架构设计以及实例分析)
MyBatis 的初始化
MyBatis 的初始化的过程其实就是解析配置文件和初始化 Configuration 的过程,MyBatis 的初始化过程可用以下几行代码来表述:
String resource = "mybatis.xml";
// 加载 mybatis 的配置文件(它也加载关联的映射文件)
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 构建 sqlSession 的工厂
sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
首先会创建 SqlSessionFactory 建造者对象,然后由它进行创建 SqlSessionFactory。这里用到的是建造者模式,建造者模式最简单的理解就是不手动 new 对象,而是由其他类来进行对象的创建。
// SqlSessionFactoryBuilder 类 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); return build(parser.parse()); // 开始进行解析了 :) } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset(); try {inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
XMLConfigBuilder 对象会进行 XML 配置文件的解析,实际为 configuration 节点的解析操作。
// XMLConfigBuilder 类 public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once.");} parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631<span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 处理environments节点数据 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> environmentsElement(root.evalNode(</span>"environments"<span style="color: rgba(0, 0, 0, 1)">)); databaseIdProviderElement(root.evalNode(</span>"databaseIdProvider"<span style="color: rgba(0, 0, 0, 1)">)); typeHandlerElement(root.evalNode(</span>"typeHandlers"<span style="color: rgba(0, 0, 0, 1)">)); mapperElement(root.evalNode(</span>"mappers"<span style="color: rgba(0, 0, 0, 1)">)); } </span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e) { </span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> BuilderException("Error parsing SQL Mapper Configuration. Cause: " +<span style="color: rgba(0, 0, 0, 1)"> e, e); }
}
在 configuration 节点下会依次解析 properties/settings/.../mappers 等节点配置。在解析 environments 节点时,会根据 transactionManager 的配置来创建事务管理器,根据 dataSource 的配置来创建 DataSource 对象,这里面包含了数据库登录的相关信息。在解析 mappers 节点时,会读取该节点下所有的 mapper 文件,然后进行解析,并将解析后的结果存到 configuration 对象中。
// XMLConfigBuilder 类 private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default");} for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) {</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 创建事务管理器 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> TransactionFactory txFactory </span>= transactionManagerElement(child.evalNode("transactionManager"<span style="color: rgba(0, 0, 0, 1)">)); DataSourceFactory dsFactory </span>= dataSourceElement(child.evalNode("dataSource"<span style="color: rgba(0, 0, 0, 1)">)); DataSource dataSource </span>=<span style="color: rgba(0, 0, 0, 1)"> dsFactory.getDataSource(); </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 建造者模式 设计模式 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> Environment.Builder environmentBuilder </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); } } }
}
// 解析单独的 mapper 文件
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse(); // 开始解析 mapper 文件了 :)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
解析完 MyBatis 配置文件后,configuration 就初始化完成了,然后根据 configuration 对象来创建 SqlSession,到这里时,MyBatis 的初始化的征程已经走完了。
// SqlSessionFactoryBuilder 类 public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
MyBatis 的 SQL 查询流程
SQL 语句的执行才是 MyBatis 的重要职责,该过程就是通过封装 JDBC 进行操作,然后使用 Java 反射技术完成 JavaBean 对象到数据库参数之间的相互转换,这种映射关系就是有 TypeHandler 对象来完成的,在获取数据表对应的元数据时,会保存该表所有列的数据库类型,大致逻辑如下所示:
/* Get resultSet metadata */ ResultSetMetaData metaData = resultSet.getMetaData(); int column = metaData.getColumnCount();for (int i = 1; i <= column; i++) {
JdbcType jdbcType = JdbcType.forCode(metaData.getColumnType(i));
typeHandlers.add(TypeHandlerRegistry.getTypeHandler(jdbcType));columnNames.add(metaData.getColumnName(i));
}
使用如下代码进行 SQL 查询操作:
sqlSession = sessionFactory.openSession();
User user = sqlSession.selectOne("com.luo.dao.UserDao.getUserById", 1);
System.out.println(user);
创建 sqlSession 的过程其实就是根据 configuration 中的配置来创建对应的类,然后返回创建的 sqlSession 对象。调用 selectOne 方法进行 SQL 查询,selectOne 方法最后调用的是 selectList,在 selectList 中,会查询 configuration 中存储的 MappedStatement 对象,mapper 文件中一个 sql 语句的配置对应一个 MappedStatement 对象,然后调用执行器进行查询操作。
// DefaultSqlSession 类 public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. 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; } }public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
执行器在 query 操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。
// CachingExecutor 类 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { /* 获取关联参数的 sql,boundSql */ BoundSql boundSql = ms.getBoundSql(parameterObject); /* 创建 cache key 值 */ CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }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);
}private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
/**
* 先往 localCache 中插入一个占位对象,这个地方
*/
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 往缓存中写入数据,也就是缓存查询结果 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> localCache.putObject(key, list); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (ms.getStatementType() ==<span style="color: rgba(0, 0, 0, 1)"> StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> list;
}
真正的 doQuery 操作是由 SimplyExecutor 代理来完成的,该方法中有 2 个子流程,一个是 SQL 参数的设置,另一个是 SQL 查询操作和结果集的封装。
// SimpleExecutor 类 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);</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 子流程1: SQL查询参数的设置 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> stmt </span>=<span style="color: rgba(0, 0, 0, 1)"> prepareStatement(handler, ms.getStatementLog()); </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 子流程2: SQL查询操作和结果集封装 </span><span style="color: rgba(0, 128, 0, 1)">*/</span> <span style="color: rgba(0, 0, 255, 1)">return</span> handler.<E><span style="color: rgba(0, 0, 0, 1)">query(stmt, resultHandler); } </span><span style="color: rgba(0, 0, 255, 1)">finally</span><span style="color: rgba(0, 0, 0, 1)"> { closeStatement(stmt); }
}
子流程 1 SQL 查询参数的设置:
首先获取数据库 connection 连接,然后准备 statement,然后就设置 SQL 查询中的参数值。打开一个 connection 连接,在使用完后不会 close,而是存储下来,当下次需要打开连接时就直接返回。
// SimpleExecutor 类 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; /* 获取 Connection 连接 */ Connection connection = getConnection(statementLog);</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 准备Statement </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> stmt </span>=<span style="color: rgba(0, 0, 0, 1)"> handler.prepare(connection, transaction.getTimeout()); </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)"> 设置SQL查询中的参数值 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> handler.parameterize(stmt); </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> stmt;
}
// DefaultParameterHandler 类
public void setParameters(PreparedStatement ps) {
/**
* 设置 SQL 参数值,从 ParameterMapping 中读取参数值和类型,然后设置到 SQL 语句中
*/
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);
}
}
}
}
}
子流程 2 SQL 查询结果集的封装:
// SimpleExecutor 类 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行查询操作 ps.execute(); // 执行结果集封装 return resultSetHandler.<E> handleResultSets(ps); }// DefaultReseltSetHandler 类
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());</span><span style="color: rgba(0, 0, 255, 1)">final</span> List<Object> multipleResults = <span style="color: rgba(0, 0, 255, 1)">new</span> ArrayList<Object><span style="color: rgba(0, 0, 0, 1)">(); </span><span style="color: rgba(0, 0, 255, 1)">int</span> resultSetCount = 0<span style="color: rgba(0, 0, 0, 1)">; </span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> * 获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等。 * 这些信息都存储在了ResultSetWrapper中了 </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)"> ResultSetWrapper rsw </span>=<span style="color: rgba(0, 0, 0, 1)"> getFirstResultSet(stmt); List</span><ResultMap> resultMaps =<span style="color: rgba(0, 0, 0, 1)"> mappedStatement.getResultMaps(); </span><span style="color: rgba(0, 0, 255, 1)">int</span> resultMapCount =<span style="color: rgba(0, 0, 0, 1)"> resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); </span><span style="color: rgba(0, 0, 255, 1)">while</span> (rsw != <span style="color: rgba(0, 0, 255, 1)">null</span> && resultMapCount ><span style="color: rgba(0, 0, 0, 1)"> resultSetCount) { ResultMap resultMap </span>=<span style="color: rgba(0, 0, 0, 1)"> resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">); rsw </span>=<span style="color: rgba(0, 0, 0, 1)"> getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount</span>++<span style="color: rgba(0, 0, 0, 1)">; } String[] resultSets </span>=<span style="color: rgba(0, 0, 0, 1)"> mappedStatement.getResultSets(); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (resultSets != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { </span><span style="color: rgba(0, 0, 255, 1)">while</span> (rsw != <span style="color: rgba(0, 0, 255, 1)">null</span> && resultSetCount <<span style="color: rgba(0, 0, 0, 1)"> resultSets.length) { ResultMapping parentMapping </span>=<span style="color: rgba(0, 0, 0, 1)"> nextResultMaps.get(resultSets[resultSetCount]); </span><span style="color: rgba(0, 0, 255, 1)">if</span> (parentMapping != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) { String nestedResultMapId </span>=<span style="color: rgba(0, 0, 0, 1)"> parentMapping.getNestedResultMapId(); ResultMap resultMap </span>=<span style="color: rgba(0, 0, 0, 1)"> configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, </span><span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">, parentMapping); } rsw </span>=<span style="color: rgba(0, 0, 0, 1)"> getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount</span>++<span style="color: rgba(0, 0, 0, 1)">; } } </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> 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); } }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());
}
}private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// createResultObject 为新创建的对象,数据表对应的类
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
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;
rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
}
return rowValue;
}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 类封装过程。最后贴一张调用栈到达 Java 类的 set 方法中的快照:
MyBatis 缓存
MyBatis 提供查询缓存,用于减轻数据库压力,提高性能。MyBatis 提供了一级缓存和二级缓存。
一级缓存是 SqlSession 级别的缓存,每个 SqlSession 对象都有一个哈希表用于缓存数据,不同 SqlSession 对象之间缓存不共享。同一个 SqlSession 对象对象执行 2 遍相同的 SQL 查询,在第一次查询执行完毕后将结果缓存起来,这样第二遍查询就不用向数据库查询了,直接返回缓存结果即可。MyBatis 默认是开启一级缓存的。
二级缓存是 mapper 级别的缓存,二级缓存是跨 SqlSession 的,多个 SqlSession 对象可以共享同一个二级缓存。不同的 SqlSession 对象执行两次相同的 SQL 语句,第一次会将查询结果进行缓存,第二次查询直接返回二级缓存中的结果即可。MyBatis 默认是不开启二级缓存的,可以在配置文件中使用如下配置来开启二级缓存:
<settings> <setting name="cacheEnabled" value="true"/> </settings>
当 SQL 语句进行更新操作 (删除 / 添加 / 更新) 时,会清空对应的缓存,保证缓存中存储的都是最新的数据。MyBatis 的二级缓存对细粒度的数据级别的缓存实现不友好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用 mybatis 的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为 mybaits 的二级缓存区域以 mapper 为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存,具体业务具体实现。