当前位置: 首页 > article >正文

第 3 章 核心处理层(中)

3.3 ResultSetHandler

MyBatis 将结果集按照映射配置文件中定义的映射规则,例如节点、resultType 属性等,映射成相应的结果对象。这一过程是由 ResultSetHandler 完成的。

public interface ResultSetHandler {

    // 处理结果集,生成相应的结果对象集合
    <E> List<E> handleResultSets(Statement stmt) throws SQLException;

    // 处理结果集,返回相应的游标对象
    <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

    // 处理存储过程的输出参数
    void handleOutputParameters(CallableStatement cs) throws SQLException;

}

DefaultResultSetHandler 是 MyBatis 提供的 ResultSetHandler 的唯一实现。

3.3.1 handleResultSets()方法

通过 select 语句查询数据库得到的结果集由 handleResultSets() 方法进行处理。

3.3.2 ResultSetWrapper

DefaultResultSetHandler 在获取 ResultSet 对象之后,将其封装成 ResultSetWrapper 对象进行处理。

3.3.3 简单映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
    // 默认的 ResultContext 对象
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // 根据 RowBounds 中的 offset 定位到指定的记录
    skipRows(resultSet, rowBounds);
    // 检测已经处理的行数是否达到上限(RowBounds 中的 limit)以及 ResultSet 中是否还有更多的记录
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        // 根据该行记录以及 ResultMap.discriminator 中的配置,解析出对应的 ResultMap
        ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 根据最终确定的 ResultMap,对该行记录进行映射,得到映射结果
        Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        // 将映射结果存储到 resultHandler.resultList 中
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
}
3.3.4 嵌套映射
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // 创建 DefaultResultContext 对象
    final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // 根据 RowBounds 中的 offset 定位到指定的记录
    skipRows(resultSet, rowBounds);
    Object rowValue = previousRowValue;
    // 检测 ResultSet 中是否还有更多的记录
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        // 通过 resolveDiscriminatedResultMap() 方法解析出最终确定的 ResultMap
        final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 为当前行记录创建 CacheKey 对象
        final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
        // 从 nestedResultObjects 中获取该行记录对应的嵌套映射的映射结果
        Object partialObject = nestedResultObjects.get(rowKey);
        // 检测 resultOrdered 是否为 true
        if (mappedStatement.isResultOrdered()) {
            if (partialObject == null && rowValue != null) {
                // 主结果对象发生变化
                // 清空 nestedResultObjects 集合
                nestedResultObjects.clear();
                // 保存主结果对象,也就是嵌套的外层对象
                storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
            }
            // 完成该行记录的映射,将映射结果存储到 nestedResultObjects 集合中
            rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        } else {
            // 完成该行记录的映射,将映射结果存储到 nestedResultObjects 集合中
            rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
            if (partialObject == null) {
                // 保存结果对象
                storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
            }
        }
    }
    // 对 resultOrdered 为 true 的情况进行特殊处理,调用 storeObject() 方法保存结果对象
    if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
        storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        previousRowValue = null;
    } else if (rowValue != null) {
        previousRowValue = rowValue;
    }
}
3.3.5 嵌套查询&延迟加载

“延迟加载”是指嵌套查询时,作为被嵌套属性的某个对象,直到真正被使用时,才会执行数据库操作加载到内存中。一个属性是否延迟加载,有两个地方配置:

  1. 中明确地配置了【fetchType】

    <resultMap id="detailedBlogResultMap" type="Blog">
        <!-- 对象属性的映射,同时也是一个嵌套映射 -->
        <association property="author" resultMap="authorResultMap" fetchType="eager"/>
        <!-- 集合属性的映射,也是一个匿名的嵌套映射 -->
        <collection property="posts" ofType="Post" fetchType="lazy">
            <id column="post_id" property="id"/>
            <result column="post_content" property="content"/>
        </collection>
    </resultMap>
    
  2. mybatis-config.xml 配置文件

    <settings>
        <!-- 打开延迟加载的功能 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 将积极的延迟加载改变为消极的加载模式 -->
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    

MyBatis 中延迟加载是通过动态代理实现的,但是由于被代理对象通过是普通的 JavaBean,没有实现任何接口,所以无不使用 JDK 动态代理。MyBatis 提供了另外两种可以为普通 JavaBean 动态生成代理对象的方式。

1. cglib

cglib 采用字节码技术实现动态代理功能,其原理是通过字节码技术为目标类生成一个子类,并在该子类中采用方法拦截的方式拦截父类方法的调用,从而实现代理的功能。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    // cglib 中的 Enhancer 对象
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class<?> clazz) {
        // 设置创建子类的类
        enhancer.setSuperclass(clazz);
        // 设置回调
        enhancer.setCallback(this);
        // 创建子类对象代理
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("前置处理");
        // 调用父类方法
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("后置处理");
        return result;
    }
}
public class CgLibTest {

    /**
     * 目标方法
     * @param str
     * @return
     */
    public String method(String str) {
        System.out.println(str);
        return "CgLibTest method(): " + str;
    }

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        CgLibTest proxyImp = (CgLibTest) proxy.getProxy(CgLibTest.class);
        String result = proxyImp.method("test");
        System.out.println(result);
        /**
         * 前置处理
         * test
         * 后置处理
         * CgLibTest method(): test
         */
    }
}

2. Javassist

Javassist 是一个开源的生成 Java 字节码的类库,其主要优点在于简单、快速,直接使用 API 就能动态修改类的结构,或是动态地生成类。

public class JavassistMain {
    public static void main(String[] args) throws Exception {
        // 创建类池
        ClassPool cp = ClassPool.getDefault();
        // 要生成的类名为 com.example.chapter3.section3.JavassistTest
        CtClass clazz = cp.makeClass("com.example.chapter3.section3.JavassistTest");

        StringBuffer body;
        // 创建字段、指定了字段名和字段类型、字段所属的类
        CtField field = new CtField(cp.get("java.lang.String"), "prop", clazz);
        // 指定该字段为 private
        field.setModifiers(Modifier.PRIVATE);
        // 设置字段的getter和setter方法
        clazz.addMethod(CtNewMethod.setter("setProp", field));
        clazz.addMethod(CtNewMethod.getter("getProp", field));
        // 设置 prop 字段的初始化值,并将字段添加到类中
        clazz.addField(field, CtField.Initializer.constant("MyName"));
        // 创建构造方法,指定了构造方法的参数、构造方法所属的类
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        // 设置构造方法的方法体
        body = new StringBuffer();
        body.append("{\n prop=\"MyName\";\n}");
        constructor.setBody(body.toString());
        // 将构造方法添加到类中
        clazz.addConstructor(constructor);
        // 创建 execute方法,指定了方法的返回值类型、方法名、方法所属的类
        CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, clazz);
        // 设置方法的访问修饰符
        ctMethod.setModifiers(Modifier.PUBLIC);
        // 设置方法体
        body = new StringBuffer();
        body.append("{\n System.out.println(\"execute(): \" + this.prop);\n}");
        ctMethod.setBody(body.toString());
        // 将方法添加到类中
        clazz.addMethod(ctMethod);
        // 生成的类写入文件
        clazz.writeFile("D:\\Gitee\\notes\\MyBatis\\MyBatis技术内幕\\MyBatis-Tec-Inside\\src\\main\\java");

        // 加载 clazz 类,并创建对象
        Class<?> c = clazz.toClass();
        Object obj = c.newInstance();
        // 调用 execute 方法
        Method method = obj.getClass().getMethod("execute", new Class[]{});
        method.invoke(obj, new Object[]{});
    }
}

执行上述代码后,在指定目录下可以找到生成的 JavassistTest.class 文件

Javassist 也是通过创建目标类的子类方式实现动态代理功能的。

public class JavassistMain2 {
    public static void main(String[] args) throws Exception {
        ProxyFactory proxyFactory = new ProxyFactory();
        // 指定父类,ProxyFactory 会动态生成一个子类
        proxyFactory.setSuperclass(JavassistTest.class);
        // 设置过滤器,判断哪些方法调用需要被拦截
        proxyFactory.setFilter(m -> {
            // 拦截 execute 方法
            if (m.getName().equals("execute")) {
                return true;
            }
            return false;
        });
        // 设置拦截处理
        proxyFactory.setHandler((self, thisMethod, proceed, params) -> {
            System.out.println("前置处理");
            Object result = proceed.invoke(self, params);
            System.out.println("执行结果:" + result);
            System.out.println("后置处理");
            return result;
        });

        // 生成代理类
        Class<?> c = proxyFactory.createClass();
        JavassistTest javassistTest = (JavassistTest) c.newInstance();
        // 调用 execute 方法,会被拦截
        javassistTest.execute();
        System.out.println(javassistTest.getProp());
    }
}

完整代码

3.3.6 多结果集处理

3.4 KeyGenerator

默认情况下,insert 语句并不会返回自动生成的主键,而是返回插入记录的条数。MyBatis 提供 KeyGenerator 接口来获取插入记录时产生的自增主键。

public interface KeyGenerator {

	// 在执行 insert 之前执行,设置属性 order="BEFORE"
    void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

    // 在执行 insert 之后执行,设置属性 order="AFTER"
    void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}

在这里插入图片描述

3.4.1 Jdbc3KeyGenerator

Jdbc3KeyGenerator 用于取回数据库生成的自增 id,它对应于 mybatis-config.xml 配置文件中的 useGeneratedKeys 全局配置,以及映射配置文件中 SQL 节点的 useGeneratedKeys 属性。

3.4.2 SelectKeyGenerator

SelectKeyGenerator 用于不支持自动生成自增主键的数据库。

3.5 StatementHandler

StatementHandler 接口是 MyBatis 的核心接口之一,它的功能很多,例如创建 Statement 对象,为 SQL 语句绑定实参,执行 select、insert、update、delete 等多种类型的 SQL 语句,批量执行 SQL 语句,将结果集映射成结果对象。

public interface StatementHandler {

    // 从连接中获取一个 Statement
    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
    // 绑定 statement 执行时所需的实参
    void parameterize(Statement statement) throws SQLException;
    // 批量执行 SQL 语句
    void batch(Statement statement) throws SQLException;
	// 执行 update/insert/delete 语句
    int update(Statement statement) throws SQLException;
	// 执行 select 语句
    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;
    
    <E> Cursor<E> queryCursor(Statement statement) throws SQLException;
    
    BoundSql getBoundSql();
    
    ParameterHandler getParameterHandler();
}

在这里插入图片描述

3.5.1 RoutingStatementHandler

RoutingStatementHandler 会根据 MappedStatement 中指定的 statementType 字段,创建对应的 StatementHandler 接口实现。

3.5.2 BaseStatementHandler

BaseStatementHandler 是一个实现了 StatementHandler 接口的抽象类,它只提供了一些参数绑定相关的方法,并没有实现操作数据库的方法。

3.5.3 ParameterHandler

ParameterHandler 只定义了一个 setParameters() 方法,主要负责调用 PreparedStatement.set*() 方法为 SQL 语句绑定实参。

3.5.4 SimpleStatementHandler

SimpleStatementHandler 继承了 BaseStatementHandler 抽象类。它底层使用 java.sql.Statement 对象来完成数据库的相关操作。

3.5.5 PreparedStatementHandler

PreparedStatementHandler 底层依赖于 java.sql.PreparedStatement 对象来完成数据库的相关操作。


http://www.kler.cn/a/511708.html

相关文章:

  • MySQL 很重要的库 - 信息字典
  • 【华为路由/交换机的ftp文件操作】
  • PortSwigger靶场练习---第二关-查找和利用未使用的 API 端点
  • 项目太大导致 git clone 失败
  • HTML<bdo>标签
  • 5 分钟复刻你的声音,一键实现 GPT-Sovits 模型部署
  • Elixir语言的文件操作
  • 【初阶数据结构】探索数据的多米诺链:单链表
  • 跳石头,,
  • 【机器学习】嘿马机器学习(科学计算库)第11篇:Pandas,学习目标【附代码文档】
  • TensorFlow深度学习实战——情感分析模型
  • SpringCloud系列教程:微服务的未来(十四)网关登录校验、自定义过滤器GlobalFilter、GatawayFilter
  • 应急管理大数据指挥中心解决方案
  • HUDI-0.11.0 BUCKET index on Flink 特性试用
  • C语言数组与字符串操作全解析:从基础到进阶,深入掌握数组和字符串处理技巧
  • 数智化转型 | 星环科技Defensor 助力某银行数据分类分级
  • 在k8s中部署一个可外部访问的Redis Sentinel
  • Pix2Pix :用于图像到图像转换的条件生成对抗网络
  • 第八篇:监视`ref`定义的【基本类型】数据
  • qt for android 报错解决记录
  • 嵌入式Linux驱动开发之platform
  • 深度学习学习笔记(第30周)
  • C语言之斗地主游戏
  • 简述1个业务过程:从客户端调用接口,再到调用中间件(nacos、redis、kafka、feign),数据库的过程
  • 【HarmonyOS NAPI 深度探索11】搭建 NAPI 开发环境:HarmonyOS DevEco Studio 全指南
  • PortSwigger NoSQL 注入