MyBatis handleResultSet结果集解析过程示例

目录
  • 前置知识
    • ResultMap和ResultMapping
  • ResultHandler和ResultContext
  • 简单映射
    • handleRowValues
    • handleRowValuesForSimpleResultMap
    • getRowValue
  • storeObject
  • 嵌套映射
    • handleRowValuesForNestedResultMap
    • getRowValue
    • applyNestedResultMappings
  • 小结

前置知识

mybatis版本: 3.5.12

之前说到mybatis执行完SQL后的结果集是由DefaultResultHandler组件的handleResultSets处理的。今天就来探讨下这个重要的方法,该方法非常核心,而且内容比较多,所以单拎出来一章。而上文DefaultResultHandler处理结果集的大致流程请参考:MyBatis ResultSetHandler 结果集的解析过程

在理解handleResultSets是怎么对结果集进行处理时,需要明白MyBatis中这么几个组件

  • ResultMap和ResultMapping
  • ResultHandler和ResultContext
  • ResultLoaderMap和ResultLoader(这些是延时加载相关)
  • ProxyFactory(延时加载相关,用来为延时加载对象创建代理对象使用的)

ResultMap和ResultMapping

我们经常使用MyBatis的xml文件配置SQL信息,在select标签上有一个属性是resultMap,它会指向一个resultMap标签,resultMap标签示例如下

<resultMap id="usermap" type="user" autoMapping="true">
    <!--关闭自动映射,那么没有指定的列名不会出现在结果集中-->
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="password" column="password"/>
</resultMap>

mybatis中每个resultMap标签会被解析成ResultMap对象。而resultMap标签中的idresultconstructor这3种子标签会被解析成ResultMapping对象(id其实就是特殊的result标签)。

接下来我们来看下ResultMap的重要组成部分

public class ResultMap {
  private Configuration configuration;
  // 唯一ID,一般是namespace+resultMap标签的ID
  private String id;
  // 对应的Java类型
  private Class<?> type;
  // resultMap标签中的result标签的集合
  private List<ResultMapping> resultMappings;
  // resultMap标签中的id标签的集合
  private List<ResultMapping> idResultMappings;
  // resultMap标签中的constructor标签的集合
  private List<ResultMapping> constructorResultMappings;
  // resultMap标签中所有子标签的集合
  private List<ResultMapping> propertyResultMappings;
  // 结果集中列名
  private Set<String> mappedColumns;
  // 对应的Java对象的属性名
  private Set<String> mappedProperties;
  // discriminator鉴别器会单独被解析成该对象
  private Discriminator discriminator;
  // 是否是嵌套映射
  private boolean hasNestedResultMaps;
  // 是否嵌套查询
  private boolean hasNestedQueries;
  // 是否自动映射
  private Boolean autoMapping;
}

其中比较重要的几个属性就是

  • resultMappings:它表示resultMap标签中的result标签的集合(包含id标签)后续结果集的解析过程避免不了会遍历它
  • hasNestedResultMaps:是否由嵌套映射,一般都是resumtMap标签中含有collection标签association标签才会为true,后面介绍到嵌套映射会分析。

ResultHandler和ResultContext

在mybatis的解析过程中,我们很容易想象到它是遍历结果集然后和resultMap标签的映射关系进行比较,最终生成结果集对象。那么遍历结果集其实就是对一条一条数据进行单独处理。处理完一条记录,就把这条记录对应的对象添加到一个集合中,最终获取这个集合就是用户想要得到的对象。

ResultContext用来在上下文中传递解析过后的单个对象,ResultHandler内部有一个List集合用来存储单条数据解析后的对象的。他们的源码非常简单易懂,首先我们来看DefaultResultHandler(ResultHandler的实现类)的源码

public class DefaultResultHandler implements ResultHandler<Object> {
  private final List<Object> list;
  @Override
  public void handleResult(ResultContext<?> context) {
    list.add(context.getResultObject());
  }
  public List<Object> getResultList() {
    return list;
  }
}

它只有一个属性listlist就是用来存储解析单条数据后的对象。它还提供了两个方法

  • handleResult:就是把上下文中单条数据处理后的对象存入到list集合中。
  • getResultList:获取结果集解析过后的list,也就是用户最终需要的list对象。

接下来来看下ResultContext这个上下文对象

public class DefaultResultContext<T> implements ResultContext<T> {
  private T resultObject;
  private int resultCount;
  private boolean stopped;
  public T getResultObject() {
    return resultObject;
  }
  public void nextResultObject(T resultObject) {
    resultCount++;
    this.resultObject = resultObject;
  }
}

它由三个属性:

  • resultObject:用来保存当前解析的这条记录的结果
  • resultCount:结果集的数量,每解析完一条记录,该值加一
  • stopped:是否停止解析,当到最后一条记录时,该值为false

它的两个方法比较简单,就不再说了。

接下来我们正式进入mybatis解析结果集的核心代码分析。mybatis解析结果集可以分为两大类:一是简单映射、而是嵌套映射。接下来我们即从这两个方面研究

简单映射

接下来就来看下本文主题handleResultSet方法的核心逻辑

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    if (parentMapping != null) {
      // 1. 处理resultSets标签的逻辑
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      // 2. 正常处理resultMap标签。将结果集中的记录映射为指定对象。可能涉及到嵌套映射。
      if (resultHandler == null) {
        // 默认都会走进该分支。
        // ResultSet是个集合嘛,那Result自然就是集合中的一条记录啦。DefaultResultHandler是用来处理单条Result记录的。
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        // 处理多行记录的方法(重要!!!!!!!)
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        // 如果用户自定义了 resultHandler 使用用户自定义的
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
}

这个方法简介明了,它的执行流程是这样的:

第一步:判断parentMapping是否存在值。而这个分支是专门为解析resultSets标签使用的分支,其实和普通的解析结果集没什么不同。等看完后面的解析过程再回过头看就理解了。这个分支不重要,我们继续往下走

第二步:判断用户是否指定resultHandler类型,一般都是不会指定的,所以他会走第一个分支。创建一个默认的ResultHandler,然后执行handleRowValues方法

第三步:如果用户指定resultHandler类型,就执行handleRowValues方法

可以发现无论是哪个分支,最终都会走到handleRowValues方法,从名字也能看出,该方法的逻辑是处理多行数据。接下来我们就来看下DefaultResultSetHandler#handleRowValues方法

handleRowValues

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler&lt;?&gt; 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);
  }
}

该方法只做了一件事情,那就是判断结果集是否是嵌套映射的。这取决于xml文件中resultMap标签是否定义了collectionassociation标签。这里我们先讨论简单映射的场景。所以接下来我们来看DefaultResultSetHandler#handleRowValuesForSimpleResultMap方法。从名字也可以看出,该方法的逻辑是处理简单映射。简单映射不会涉及延迟加载等复杂的逻辑,所以源码很好理解

handleRowValuesForSimpleResultMap

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  // 第一步:经过resultHandler处理过后,每行记录会被映射成一个对象。该对象暂存在 这个Result上下文中。(暂存DefaultResultContext)
  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  ResultSet resultSet = rsw.getResultSet();
  // 跳过指定的偏移量,相当于limit只不过他是内存分页,一般用不到(RowBounds中指定的偏移量)
  skipRows(resultSet, rowBounds);
  // 第二步:结果集中会有很多记录,通过该循环处理结果集中的每条记录。每个记录最后都会存储到ResultHandler的List集合中。
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    // 处理discrimination标签。类似于Java中的switch语法。如果resultMap标签有鉴别器。则根据case的情况动态的获取resultMap映射结果集
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    // 第三步:获取记录映射的对象(重要!!!!!!)
    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
    // 第四步:把对象存储到上下文中。
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }
}

下面是该方法的执行逻辑

第一步:定义ResultContext对象(前文说过),它用来暂存每一行记录的处理结果的对象。从ResultSetWrapper中获取JDBC的结果集对象ResultSet。skipRows方法跳过指定的内存分页(一般没什么用,忽略该方法即可,研究价值不大)

第二步:遍历结果集,通过resolveDiscriminatedResultMap方法得到鉴别器鉴别的ResultMap对象,其实本质上还是一个ResultMap(有关鉴别器的内容请参考另一篇文章:MyBatis discriminator标签 实战和原理解析

第三步:调用getRowValue方法处理每一行数据并得到结果对象

第四步:把上一步生成的对象通过上下文ResultContext添加到ResultHandler中。(再次提示:ResultHandler对象里面存放结果集解析后的对象集合哦)

其中,重点操作在第三步和第四步。接下来我们就先来聊聊getRowValue方法是如何处理一行记录的。

getRowValue

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
  // (重要方法!!!!!!!!!!!!)比如返回结果ResultType指定的是map,那么这里就会创建一个空Map对象,下面的if才是给map里添加元素。
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
      // (重要方法!!!!!!!!!!!!)是否应该自动映射。比如说SQL返回结果集有10列,但是resultMap只配置了9个字段,剩下的那个字段,如果满足返回列与映射列名称相同,就会自动进行映射
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
    }
    // (重要方法!!!!!!!!!!!!)处理resultMap标签中的映射。把结果集根据resultMap中的映射关系生成最终的结果对象。属性会被设置到metaObject中
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    // isReturnInstanceForEmptyRow是否启用。如果开启则返回空对象,否则返回null。
    // 注:这个空对象依赖于createResultObject方法创建出的对象是空还是null。
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

该方法处理逻辑如下(延时加载的代码请忽略,简单映射不会涉及到延时加载,这里之所以会出现ResultLoaderMap这样延时加载相关的对象,是因为嵌套映射方法也会调用这个方法解析结果集。)

第一步:首先通过createResultObject对象创建一个空壳对象,啥是空壳对象呢?比如说xml文件中的resultMap标签定义了返回值类型是User类型,那么这里就会创建一个User对象,但是属性值全为空。

当然createResultObject背后的逻辑其实很复杂,它会首先判断用户是否在resultMap标签中定义了构造器标签,如果没定义才会按上述流程描述所说的创建一个空壳对象。但如果用户在resultMap标签中定义了构造器标签,那么就会直接将结果集中的数据通过构造器构造出完整的对象。这样也很能理解吗,毕竟调用构造器是要传值的。

第二步:通过applyAutomaticMappings方法完成自动映射,啥是自动映射嘞。比如一条sql返回的列由name age两列,但是resultMap标签中只定义了一个映射关系name,那么age列就会自动寻找映射关系,如果发现列名age与resultMap返回类型的属性同名,就会自动映射到该类型中。该步骤中只会映射resultMap标签中未定义的映射关系对应的字段

第三步:通过applyPropertyMappings方法完成属性映射,它是第二步的补充,完成resultMap标签中result标签的映射关系

最后:返回解析完成的值

到此,结果集中的一条记录已经被解析用户指定的类型对象了,接下来就是要把该对象返回给用户,这就要回到我们上一步的storeObject方法了。接下来来看下DefaultResultSetHandler#storeObject方法

storeObject

private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
  if (parentMapping != null) {
    // 处理ResultSets标签时走这个分支
    linkToParents(rs, parentMapping, rowValue);
  } else {
    callResultHandler(resultHandler, resultContext, rowValue);
  }
}
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  resultContext.nextResultObject(rowValue);
  ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

storeObject方法的逻辑也很简单(一般都会走到callResultHandler方法)

第一步:通过nextResultObject方法存储getRowValue方法解析的结果对象。

第二步:把单行记录的解析对象存入到ResultHandler的集合当中

嵌套映射

简单介绍完了简单映射之后,接下来我们来看下比较复杂的嵌套映射,在嵌套映射中也会有很多问题,比如:循环依赖如何解决、延迟加载如何解决。 本节只针对于一般情况下的嵌套映射做出分析,循环依赖和延时加载后续我会继续更新文章。

回想一下上一小节中提到的handleRowValues方法,它会根据ResultMap对象判断是否是嵌套映射,如果是嵌套映射,就会走入handleRowValuesForNestedResultMap方法,接下来我们就来看下handleRowValuesForNestedResultMap这个方法。

handleRowValuesForNestedResultMap

private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  // 第一步 获取上下文对象,用于存储单行处理后的记录
  final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
  ResultSet resultSet = rsw.getResultSet();
  skipRows(resultSet, rowBounds);
  Object rowValue = previousRowValue;
  // 第二步: 遍历结果集
  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
    final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
    // 嵌套映射中的结果都会被存入到nestedResultObjects中。这里的CacheKey就是缓存对象唯一标识
    final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
    // 映射过程中的对象也会被存入在此。
    Object partialObject = nestedResultObjects.get(rowKey);
    // 通常会走到该分支。获取一行记录处理后的对象(有可能是半成品)
    rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
    if (partialObject == null) {
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }
  if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    previousRowValue = null;
  } else if (rowValue != null) {
    previousRowValue = rowValue;
  }
}

其实仔细阅读这段可以发现它和简单映射的代码是有一部分雷同的,这也很好理解,嵌套映射还是基于简单映射的思想进行递归处理结果集映射关系的嘛。接下来来介绍一下代码的流程

第一步:还是获取上下文对象,获取JDBC的结果集对象。就不罗嗦了

第二步:遍历结果集,处理每一行数据

第三步:创建CacheKey对象,使用嵌套映射中的外层对象创建缓存key,为了解决一对多映射

第四步:从缓存中通过CacheKey对象获取外层对象。这里的nestedResultObjects是DefaultResultSetHandler的一个属性,他就是用来缓存嵌套映射中的外层对象的。如果是第一次循环,从缓存中获取肯定是为空的嘛。但是你想一下这种场景——一个用户(user)拥有多个订单(order),这种一堆多的关系,在同一个用户进行到循环的第二轮时,肯定不希望再创建一个外层对象,而是想让order对象都公用一个外城对象。这种情况只能添加缓存来做了。这就是nestedResultObjects的意义。

这里的partialObject就是外层对象,也可以称作是部分对象,因为它不完整嘛。

第五步:调用getRowValue方法获取行记录解析的结果

第六步:调用storeObject方法存储对象到ResultHancler中

其中最重要的是要理解第四步中的嵌套思想,以及第五步解析一行记录的详细过程。这里一定会涉及到循环调用,因为解析外层对象必然要解析内层对象嘛

getRowValue

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
  final String resultMapId = resultMap.getId();
  Object rowValue = partialObject;
  // 映射第一次肯定一定为null
  if (rowValue != null) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    putAncestor(rowValue, resultMapId);
    applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
    ancestorObjects.remove(resultMapId);
  } else {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, true)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      putAncestor(rowValue, resultMapId);
      foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
      ancestorObjects.remove(resultMapId);
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    if (combinedKey != CacheKey.NULL_CACHE_KEY) {
      nestedResultObjects.put(combinedKey, rowValue);
    }
  }
  return rowValue;
}

映射时,第一次外层对象一定为null,所以会走else分支,只有在一堆多种的第二次映射才会走到if分支。我们来看下else分支的处理逻辑

第一步:首先通过createResultObject对象创建一个空壳对象,简单映射中提过

第二步:通过applyAutomaticMappings方法完成自动映射

第三步:通过applyPropertyMappings方法完成属性映射,它是第二步的补充,完成resultMap标签中result标签的映射关系

第四步:通过applyNestedResultMappings方法完成嵌套映射

到此,结果集中的一条记录已经被嵌套解析完成了。其中和简单映射不一样的地方只有多调用了一个applyNestedResultMappings方法,完成嵌套映射。我们来看下这个方法

applyNestedResultMappings

private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
  boolean foundValues = false;
  for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
    final String nestedResultMapId = resultMapping.getNestedResultMapId();
    if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
      // 获取内层对象
      rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
      if (rowValue != null && !knownValue) {
        // 把内层对象关联到外层对象
        linkObjects(metaObject, resultMapping, rowValue);
        foundValues = true;
      }
    }
  }
  return foundValues;
}

它的执行逻辑是遍历resultMapping,对resultMapping进行getRowValue方法的调用,此时就应该属于简单影射了(除非嵌套了很多层)

它通过for循环每个resultMapping,直到找到嵌套映射的哪一个字段,然后再进行简单映射封装结果返回作为外层对象的属性值。

它的调用逻辑是getRowValue——applyNestedResultMappings——getRowValue,直到嵌套映射完成位置,嵌套多少层,这个链路就会深多少层。

小结

mybatis解析结果集是通过遍历结果集数据,并把结果集数据对照resultMap标签中的映射关系一一映射,如果存在嵌套映射则嵌套执行getRowValue获取一行记录的结果并关联到外层对象。

以上就是MyBatis handleResultSet结果集解析过程示例的详细内容,更多关于MyBatis handleResultSet 的资料请关注我们其它相关文章!

(0)

相关推荐

  • mybatis返回map结果集@MapKey使用的场景分析

    目录 mybatis返回map结果集@MapKey使用场景 使用id作为map的ke Map的value为Map,一条记录对应一个Map 使用name作为map的key mybatis使用@MapKey注解 背景和含义 具体示例 mybatis返回map结果集@MapKey使用场景 select的 resultType属性为map时: 通过MapKey指定map的key值 使用id作为map的ke @MapKey("id") Map<Long, UserInfo> getU

  • Mybatis-plus配置分页插件返回统一结果集

    目录 一.MyBatisPlusConfig中配置分页插件 1. 分页实现的原理 二.统一结果集 1. 创建返回码定义类 2. 创建结果集类 三.编写分页接口 1. 先编写查询类 2. service层 3. controller层 4. 接口测试 总结 一.MyBatisPlusConfig中配置分页插件 /** * 配置分页插件 * @return page */ @Bean public PaginationInterceptor paginationInterceptor(){ Pagi

  • mybatis调用mysql存储过程(返回参数,单结果集,多结果集)

    目录 一.接收一个返回值 注意事项: 存储过程主要分成三类: 二.接收list结果集 三.返回多个结果集 四.第二种配置也可以 一.接收一个返回值 使用Map接收返回参数,output参数放在传入的param中 创建表 DROP TABLE IF EXISTS `demo`; CREATE TABLE `demo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`

  • Mybatis实现ResultMap结果集

    ResultMap——解决属性名和字段名不一致的问题 数据库中的字段 新建一个项目,拷贝之前的,测试实体类字段不一致的情况 1.新建一个module——mybatis-03 2.新建db.properties配置文件 driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useEncoding=false&characterEncoding=UTF-8&serverTime

  • MyBatis实现两种查询树形数据的方法详解(嵌套结果集和递归查询)

    目录 方法一:使用嵌套结果集实现 1,准备工作 2,实现代码 方法二:使用递归查询实现 树形结构数据在开发中十分常见,比如:菜单数.组织树, 利用 MyBatis 提供嵌套查询功能可以很方便地实现这个功能需求.而其具体地实现方法又有两种,下面分别通过样例进行演示. 方法一:使用嵌套结果集实现 1,准备工作 (1)假设我们有如下一张菜单表 menu,其中子菜单通过 parendId 与父菜单的 id 进行关联: (2)对应的实体类如下: @Setter @Getter public class M

  • 详解MyBatis ResultSetHandler 结果集的解析过程

    目录 正文 ResultSetHandler#handleResultSets 第一部分:ResultSetWrapper 第二部分:验证rsw对象 第三部分:遍历rsw中的结果集 第四部分:处理ResultSets标签 第五部分:collapseSingleResultList 总结 正文 mybatis版本:3.5.12 mybatis通过Executor查询出结果后,通常返回的是一个List结构,再根据用户调用的API把List结构转为指定结构. 比如用户调用SqlSession#sele

  • Mybatis TypeHandler接口及继承关系示例解析

    目录 开篇 TypeHandler接口 TypeHandler继承体系 IntegerTypeHandler DateTypeHandler TypeHandlerRegistry TypeHandlerRegistry#register方法 总结 开篇 JDBC类型与Java类型并不是完全一一对应的.所以在PreparedStatement绑定参数的时候需要把Java类型转为JDBC类型.JDBC类型的枚举值在JdbcType枚举值中存储. MyBatis中提供了一个接口专用于JDBC类型与J

  • Mybatis pagehelper分页插件使用过程解析

    这篇文章主要介绍了mybatis pagehelper分页插件使用过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用过mybatis的人都知道,mybatis本身就很小且简单,sql写在xml里,统一管理和优化.缺点当然也有,比如我们使用过程中,要使用到分页,如果用最原始的方式的话,1.查询分页数据,2.获取分页长度,也就是说要使用到两个方法才能完成分页.有没有更更好的分页方式的,pagehelper分页插件因此而诞生,他的原理是利用

  • python爬虫lxml库解析xpath网页过程示例

    目录 前言 (一)xpath是什么 (二)xpath的基本语法 路径查询. (三) lxml库 (四)lxml库的使用 导入lxml.etree (五)实例演示 前言 在我们抓取网页内容的时候,通常是抓取一整个页面的内容,而我们仅仅只是需要该网页中的部分内容,那该如何去提取呢?本章就带你学习xpath插件的使用.去对网页的内容进行提取. (一)xpath是什么 xpath是一门在XML文档中查找信息的语言,xpath可用来在XML 文档中对元素和属性进行遍历,主流的浏览器都支持xpath,因为h

  • Java Mybatis框架由浅入深全解析中篇

    目录 前言 添加框架的步骤 在idea中添加数据库的可视化 添加jdbc.properties属性文件(数据库配置) 添加SqlMapCongig.xml 创建实体类Student用来封装数据 添加增删改查 创建测试类进行功能测试 总结 前言 上一篇我们了解了框架相关知识,并且导入依赖配置了核心文件,今天就可以开始写代码测试了. 添加框架的步骤 在idea中添加数据库的可视化 这里需要注意:很多小伙伴链接不成功,这个时候要修改一下自己的驱动版本,尽量与数据库版本一致 添加jdbc.propert

  • 【MyBatis源码全面解析】MyBatis一二级缓存介绍

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以把查询结果存储起来,下次查询同样的内容的时候直接从内存中获取数据即可,这样在某些场景下可以大大提升查询效率. MyBatis的缓存分为两种: 一级缓存,一级缓存是SqlSession级别的缓存,对于相同的查询,会从缓存中返回结果而不是查询数据库 二级缓存,二级缓存是Mapper

  • mybatis分页绝对路径写法过程详解

    这篇文章主要介绍了mybatis分页绝对路径写法过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 共四步, 1.下载jar包,maven的坐标为 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.0.4</version&

  • SpringBoot+MyBatis+AOP实现读写分离的示例代码

    目录 一. MySQL 读写分离 1.1.如何实现 MySQL 的读写分离? 1.2.MySQL 主从复制原理? 1.3.MySQL 主从同步延时问题(精华) 二.SpringBoot+AOP+MyBatis实现MySQL读写分离 2.1.AbstractRoutingDataSource 2.2.如何切换数据源 2.3.如何选择数据源 三 .代码实现 3.0.工程目录结构 3.1.引入Maven依赖 3.2.编写配置文件,配置主从数据源 3.3.Enum类,定义主库从库 3.4.ThreadL

  • java开发Dubbo负载均衡与集群容错示例详解

    目录 负载均衡与集群容错 Invoker 服务目录 RegistryDirectory 获取Invoker列表 监听注册中心 刷新Invoker列表 StaticDirectory 服务路由 Cluster FailoverClusterInvoker FailfastClusterInvoker FailsafeClusterInvoker FailbackClusterInvoker ForkingClusterInvoker BroadcastClusterInvoker Abstract

  • MySQL的prepare使用及遇到bug解析过程

    目录 一.问题发现 二.问题调查过程 三.问题解决方案 四.问题总结 一.问题发现 在一次开发中使用 MySQL PREPARE 以后,从 prepare 直接取 name 赋值给 lex->prepared_stmt_name 然后给 EXECUTE 用,发现有一定概率找不到 prepare stmt 的 name,于是开始动手调查问题发生的原因. SQL语句示例: CREATE TABLE t1 (a INT, b VARCHAR(10)); PREPARE dbms_sql_stmt4 F

随机推荐