MyBatis2

使用如下代码进行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 selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List list = this.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 List 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 List 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 List 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 list = (List) tcm.getObject(cache, key); if (list == null) { list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; /** * 先往localCache中插入一个占位对象,这个地方 */ localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); }

/* 往缓存中写入数据,也就是缓存查询结果 */
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
}
return list;

} 复制代码 真正的doQuery操作是由SimplyExecutor代理来完成的,该方法中有2个子流程,一个是SQL参数的设置,另一个是SQL查询操作和结果集的封装。

复制代码 // SimpleExecutor类 public List 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);

    /* 子流程1: SQL查询参数的设置 */
    stmt = prepareStatement(handler, ms.getStatementLog());

    /* 子流程2: SQL查询操作和结果集封装 */
    return handler.query(stmt, resultHandler);
} finally {
    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);

/* 准备Statement */
stmt = handler.prepare(connection, transaction.getTimeout());

/* 设置SQL查询中的参数值 */
handler.parameterize(stmt);
return stmt;

}

// DefaultParameterHandler类 public void setParameters(PreparedStatement ps) { /** * 设置SQL参数值,从ParameterMapping中读取参数值和类型,然后设置到SQL语句中 */ ErrorContext.instance().activity(“setting parameters”).object(mappedStatement.getParameterMap().getId()); List 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 List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行查询操作 ps.execute(); // 执行结果集封装 return resultSetHandler. handleResultSets(ps); }

// DefaultReseltSetHandler类 public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity(“handling results”).object(mappedStatement.getId());

final List multipleResults = new ArrayList();

int resultSetCount = 0;
/**
 * 获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等。
 * 这些信息都存储在了ResultSetWrapper中了
 */
ResultSetWrapper rsw = getFirstResultSet(stmt);

List resultMaps = mappedStatement.getResultMaps();
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++;
}

String[] resultSets = mappedStatement.getResultSets();
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);
 
}
 复制代码
 ResultSetWrapper是ResultSet的包装类,调用getFirstResultSet方法获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等,这些信息都存储在ResultSetWrapper类中了。然后调用handleResultSet方法来来进行结果集的封装。 
复制代码
 // DefaultResultSetHandler类
 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List 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 resultContext = new DefaultResultContext();
 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 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默认是不开启二级缓存的,可以在配置文件中使用如下配置来开启二级缓存: 当SQL语句进行更新操作(删除/添加/更新)时,会清空对应的缓存,保证缓存中存储的都是最新的数据。MyBatis的二级缓存对细粒度的数据级别的缓存实现不友好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为mybaits的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存,具体业务具体实现

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Mybatis学习

    MyBatis是ORM的解决方案 在对象模型和数据库表之间进行沟通的一座桥梁

    杨校
  • Mybatis传 ARRAY给Oracle存储过程

    核心代码如下(详见附件中的 handler/ArrayHandler.java):

    日薪月亿
  • Mybatis的批处理

    本文主要测试Mybatis在mysql数据库下的批量插入、批量删除、批量更新。

    用户6055494
  • SpringBoot 2.0中SpringWebContext 找不到无法使用的问题解决

    为了应对在SpringBoot中的高并发及优化访问速度,我们一般会把页面上的数据查询出来,然后放到redis中进行缓存。减少数据库的压力。

    做全栈攻城狮
  • Spring整合Mybaties

    如果实体类的熟悉名和数据库表的字段名不一致,有一种简单的方法就是给字段名取一个别名。这样不就保持了一致

    全栈程序员站长
  • 碎片化 | 第四阶段-48-hibernate概述和配置-视频

    本套视频从Java基础到架构模式以及AI算法,整体视频以“碎片化”学习的模式,提供给大家 ,并配备实际项目为案例,让大家在坐车、吃饭、午休、蹲坑的时候,都可以学...

    码神联盟
  • Web项目接口自动化测试框架搭建

    一、原理及特点 参数放在XML文件中进行管理 用httpClient简单封装一个httpUtils工具类 测试用例管理使用了testNg管理,使用了TestNG...

    用户1257215
  • 框架 | SpringBoot项目创建和发布部署步骤

    码神联盟
  • SpringBoot整合Mybatis,使用通用mapper和PageHelper进行分页

    上节介绍了如何整合Security,这节就说下如何再Springboot下使用持久层框架mybatis和牛人封装的通用mapper与mybatis的整合,直接进...

    AI码师
  • Activiti与JBPM概念和差异(整理和汇总)

    导读 | Activiti VS JBPM Activiti概念 一、Activiti特点 1、数据持久化 activiti 设计思想:简洁、快速。使用my...

    程序源代码
  • Spring Boot 引入 MyBatis

    什么是mybatis?   MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代...

    Bobby
  • [一天一个进阶系列] - MyBatis基础篇

    注意: MyBatis 中自带的这两级缓存与 MyBatis 以及整个应用是运行在同一个JVM中的,共享同一块堆内存。如果这两级缓存中的数据量较大, 则可能影响...

    用户8262580
  • 1. spring5源码 -- Spring整体脉络 IOC加载过程 Bean的生命周期

    ioc是控制反转, 这是一种设计理念, 用来解决的是层和层之间, 类和类之间的耦合问题.

    用户7798898
  • spring5源码 -- IOC容器设计理念和核心注解的作用

    ioc是控制反转, 这是一种设计理念, 用来解决的是层和层之间, 类和类之间的耦合问题.

    用户7798898
  • MyBatis框架之第二篇

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    海仔

扫码关注云+社区

领取腾讯云代金券