Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

一丶前情回顾

书接上回,下面是SimpleExecutor执行查询的主要逻辑

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
+ instantiateStatement 根据数据库连接,创建Statement,具体什么类型的Statement取决于当前用的什么StatementHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
至此我们以及将参数设置到PreparedStatement 上了
相当于
select * from A where a=#{a}
这里的a=?已经成功设置上去了

二丶执行查询

这些查询交由StatementHandler处理,可以看下图,

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
还有个RoutingStatementHandler,就是调用被装饰着的query方法

三丶处理结果集

终于到了这篇总结的重点,处理结果集,上面图可以看到Statement执行excute后,使用ResultSetHandler处理结果集,ResultSetHandler只有一个实现类 DefaultResultSetHandler

1.处理多结果集

存储过程存在多结果集的情况,

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

2.处理结果集

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

不存在嵌套子查询的时候,使用handleRowValuesForSimpleResultMap

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

这里出现一个类DefaultResultContext,实现了ResultContext,这是结果上下文,主要的职责是控制处理结果行的停止,配合rowBounds实现内存分页,后面的storeObject就是将一行对应的对象存在list(似乎对map这种出惨有特殊处理,对于嵌套子查询也有特殊处理)

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

这里的自动映射应该是处理,没有指定resultMap 凭借对象属性和数据库列名进行映射的情况 ,后面applyPropertyMappings 处理指定resultMap 中column和 property的情况

例如我们执行下面test这条sql,


        SELECT *
        FROM user
        WHERE id = #{id}

User对象是

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

对于id和name 会执行 applyAutomaticMappings赋值对应属性,对于eList执行applyPropertyMappings赋值到属性

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

四丶mybatis插件实现原理

1.拦截器接口

public interface Interceptor {

    //拦截        Invocation :当前被拦截对象 参数 和被拦截方法
  Object intercept(Invocation invocation) throws Throwable;

  //动态代理
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP  可以在这里给拦截器赋值一些属性
  }

}

2.Plugin.wrap(target, this)方法如何实现拦截

  • Plugin 实现了InvocationHandler——基于JDK动态代理

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
  • 读取注解信息,获取当前拦截器要拦截什么类的什么方法

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

注意获取方法的方式

Method method = sig.type().getMethod(sig.method(), sig.args());

getMethod方法是没有办法获取到私有方法的,所有无法拦截一个私有方法

  • 获取被代理类的接口

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
  • 动态代理对象生成

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

3.动态代理生成的对象是怎么被使用的

如上我们知道了mybatis 是怎么支持插件的,根据拦截器上的信息生成动态代理对象,动态代理对象在执行方法的时候会进入拦截器的intercept拦截方法,那么动态代理的生成的对象在哪里被使用到昵

  • Configuration 类 也就是mybatis的大管家,在new一些mybatis四大对象的时候会使用到插件

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
也就是说mybatis 只支持拦截ParmeterHandler,ResultSetHandler,StatementHandler,Excutor这四种对象

在mybatis执行中的使用的四大对象其实是被动态代理后的对象

五丶基于mybatis插件实现参数化类型TypeHandler

1.需求

  • 存在实体
@Data
public class User {
    @NotNull(message = "id")
    private Integer id;
    @NotNull(message = "name")
    private String name;

    private List eList;

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class E {
        Integer i;
        String j;
    }
}

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
* 我希望在插入User对象到数据表的时候,自动到eList转换为Json格式存入 查询User对象返回的时候,自动从Json格式字符串转换为 List<e></e>类型

2.实现TypeHandler

阅读上面结果集处理源码后,我们知道对一个字段的处理最终是使用了对应TypeHanlder进行读取,我们实现一个我们的TypeHandler实现Json的序列化和反序列化

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ParameterizeType2JsonStrTypeHandler extends BaseTypeHandler {

    //设置参数 也就是eList插入的时候怎么设置到对应的PrepareStatement占位符上
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        //直接序列化成字符串
        ps.setString(i, JSON.toJSONString(parameter));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //怎么根据字段名,返回属性值
        //比如我们这里是List,不能简单的序列化成List.class ,因为这样List内部其实存储的是JsonObject对象,没办法泛型擦除是这样的
        //那么怎么拿到参数化类型昵 我们要的是List

    }
}

这里的getNullableResult 入参只有结果集 和列名,这显然不足以让我们拿到 List<确切的类型><!--确切的类型-->

3.”拦截”ResultSetHandler

我们知道处理一行数据到对象映射使用的方法是ResultSetHandler的handleRowValues方法

加上上面实现TypeHandler遇到的问题,看来我们需要实现一个自己的ResultSetHandler,在handleRowValues执行之前把当前处理对象的class 存储到一个位置,然后我们实现的TypeHandler在getNullableResult 执行的时候拿到class 根据字段名词找到对应属性,然后获取到数据的通用类型,然后再进行Json的反序列化

  1. 如何存储结果集转换目标对象类型的class 这里我们使用ThreadLocal进行存储ResultMap 可以根据 ResultMap中的方法拿到当前的类型
public class ResultSetProcessTypeMemory {

    private final static ThreadLocal MEMORY = new ThreadLocal<>();

    public static ResultMap get() {
        return MEMORY.get();
    }

    public static void set(ResultMap resultMap) {
        MEMORY.set(resultMap);
    }

    public static void remove() {
        MEMORY.remove();
    }

}

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
public class MyResultSetHandler extends DefaultResultSetHandler {

    public MyResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql, RowBounds rowBounds) {
        super(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    }

    @Override
    public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
        //放入ThreadLocal
        ResultSetProcessTypeMemory.set(resultMap);
        super.handleRowValues(rsw, resultMap, resultHandler, rowBounds, parentMapping);
        //移除
        ResultSetProcessTypeMemory.remove();
    }
}

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
private static MyResultSetHandler create(RoutingStatementHandler routingStatementHandler) {
    BaseStatementHandler delegate = (BaseStatementHandler) ReflectUtil.getFieldValue(routingStatementHandler, "delegate");
    return create(delegate);
}

private static MyResultSetHandler create(BaseStatementHandler base) {
    Executor executor = (Executor) ReflectUtil.getFieldValue(base, "executor");
    MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(base, "mappedStatement");
    ParameterHandler parameterHandler = (ParameterHandler) ReflectUtil.getFieldValue(base, "parameterHandler");
    ResultHandler resultHandler = (ResultHandler) ReflectUtil.getFieldValue(base, "resultHandler");
    BoundSql boundSql = (BoundSql) ReflectUtil.getFieldValue(base, "boundSql");
    RowBounds rowBounds = (RowBounds) ReflectUtil.getFieldValue(base, "rowBounds");
    return new MyResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
}

public static MyResultSetHandler create(StatementHandler statementHandler) {
    if (statementHandler instanceof BaseStatementHandler){
        return create((BaseStatementHandler)statementHandler);
    }
    if (statementHandler instanceof RoutingStatementHandler){
        return create((RoutingStatementHandler)statementHandler);
    }
    throw new UnsupportedOperationException();
}

4.拦截StatementHandler

上面的一通操作,我们搞出来一个自己的ResultSetHandler 但是怎么让mybatis 用我们的ResultSetHandler昵,我们可以拦截StatementHandler的query方法

Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler
@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "query",
                args = {Statement.class, ResultHandler.class}
        )
})
public class ListVarcharTypeHandlerInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        PreparedStatement ps = (PreparedStatement) args[0];
        ps.execute();
        StatementHandler target = (StatementHandler) invocation.getTarget();
        //最后一步用我们自己的ResultSetHandler 处理
        return MyResultSetHandler.create(target).handleResultSets(ps);
    }

}

5.注入拦截器

/**
 * mybatis配置
 */
@Configuration
public class MybatisConfiguration {

    /**
     * 注册拦截器
     */
    @Bean
    public ListVarcharTypeHandlerInterceptor mybatisInterceptor() {
        return new ListVarcharTypeHandlerInterceptor();
    }

}

6.完善TypeHandler

实现完第五步,我们可以从ThreadLocal中拿到ResultMap,接下来就是如何利用Json反序列化到属性上了

@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes({List.class})
public class ParameterizeType2JsonStrTypeHandler extends BaseTypeHandler {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, JSON.toJSONString(parameter));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //从TreadLocal拿
        ResultMap resultMap = ResultSetProcessTypeMemory.get();
        //拿到封装目标类型class
        Class type = resultMap.getType();
        //根据字段名词 拿到field 这里可以优化下,忽略大小写吧
        Field field = ReflectUtil.getField(type, columnName);
        //拿到通用类型 可以拿到List
        Type genericType = field.getGenericType();
        //从resutSet拿到字符串内容,并且反序列化回去
        return JSON.parseObject(rs.getString(columnName), genericType);
    }
}

7.简单的一个测试


    //指定用我们的参数化类型TypeHandler

    SELECT *
    FROM user
    WHERE id = #{id}

最后成功反序列化且类型没有错误

Original: https://www.cnblogs.com/cuzzz/p/16609763.html
Author: Cuzzz
Title: Mybatis 源码6 结果集映射流程 ,mybatis插件实现原理和基于mybatis插件实现参数化类型TypeHandler

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/713885/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Kylin配置Spark并构建Cube

    HDP版本:2.6.4.0Kylin版本:2.5.1机器:三台 CentOS-7,8G 内存Kylin 的计算引擎除了 MapReduce ,还有速度更快的 Spark ,本文就以…

    技术杂谈 2023年7月24日
    096
  • 【leetcode】151. 颠倒字符串中的单词

    给你一个字符串 s ,颠倒字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空…

    技术杂谈 2023年7月24日
    057
  • haproxy dataplaneapi 2.6 发布

    haproxy dataplaneapi 2.6 包含了一些新特性,还是很不错的 新特性 主要还是关于haproxy 2.6 版本新特性,指令的支持 ring 指令支持 globa…

    技术杂谈 2023年5月30日
    090
  • Java-泛型

    泛型出现的原因 Java的泛型是在JDK1.5开始才加上的。在此之前的Java是没有泛型的。没有泛型的Java使用起来给人感觉非常的笨重,为了体会泛型带来的好处,来看看如果没有泛型…

    技术杂谈 2023年7月11日
    065
  • MybatisPlus拓展——实现多数据源操作

    多数据源 适用:一般工作时候会有多个数据库,每个库对应不同的业务数据。程序如果每次数据都访问同一个数据库,该数据库压力很大访问会很慢。 1、导入依赖 com.baomidou dy…

    技术杂谈 2023年7月11日
    083
  • 网站操作备忘

    ll /proc/1296892/fd/ 通过PID找到哪个文件操作的进程,进而知道问题所在 flash不能使用。安装Flash Player官方下载中心-Flash中国官网 ,在…

    技术杂谈 2023年5月31日
    0107
  • python练习题:将列表中的大写字母转换成小写

    将列表中的大写字母转换成小写如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,L1 = [‘Hello’, ‘World’, 18, ‘Apple’,…

    技术杂谈 2023年7月24日
    075
  • Rust:axum学习笔记(7) websocket

    接上一篇继续,今天来学习下如何用axum实现websocket,代码如下: Cargo.toml添加依赖项 cpp;gutter:true; [package] name = &q…

    技术杂谈 2023年5月31日
    0141
  • 如何使tmux可以像vi一样操作(如快速跳转到某一行)?

    答: 按下ctrl+b,然后输入以下配置即可: :setw mode-keys vi Original: https://www.cnblogs.com/dakewei/p/141…

    技术杂谈 2023年5月31日
    067
  • Vue基础知识汇总

    2020年初的时候突击将Vue学习了一下,因为有不错的HTML、CSS、JS 基础,以及微信小程序的编程知识,Vue学起来是真的快·,三下五除二,将Vue官网的教程文章搞完了,并完…

    技术杂谈 2023年7月11日
    076
  • 2021年扩展DevOps的6种方法

    2021年扩展DevOps的6种方法 加强devops流程的自动化 为了满足快速、高质量应用程序交付的需求,现代软件团队需要一种超越常规性能测试的方法。在这里,以devops为中心…

    技术杂谈 2023年5月31日
    073
  • 怎么有效解决“未能创建 SSL/TLS 安全通道”异常

    之前写了一个服务自动程序,程序会访问第三方的一个https接口,一直用的好好的,今天突然报错了,异常就发生在访问接口的地方,”请求被中止,未能创建 SSL/TLS 安全…

    技术杂谈 2023年5月31日
    0227
  • Mysql基础篇-查询、函数、多表、事务

    基础篇 1.1 mysql用户和权限管理 查询用户 USER mysql; SELECT * FROM user; 创建用户 CREATE USER ‘&#x7528;&a…

    技术杂谈 2023年7月24日
    075
  • lambda表达式捕获变量的生命周期

    在C++11中,lambda表达式有两种变量捕获方式,分别为值捕获和引用捕获。这两种捕获的形式如下: #include <iostream> int main(int …

    技术杂谈 2023年5月30日
    099
  • PowerBI发布到网页

    如果网页当中需要嵌入PowerBI的报表,可以在PowerBI当中生成链接,然后网页或者博客当中插入这一段html代码。 以下是PowerBI生产网页链接的示例,并且在博客的最后也…

    技术杂谈 2023年5月31日
    0158
  • Django显示本地图片,注意事项

    1、在url.py文件中的配置 导入相关的库,在Python2.0后,要用re_path from django.urls import path,re_path from dja…

    技术杂谈 2023年7月24日
    0100
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球