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/609784/

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

(0)

大家都在看

  • 反射取得静态类中的属性,方法,字段

    欢迎加我的QQ群:193522571,一起来讨论、交流! Type BstrType = typeof(CadBaseSet);DataTable dt = (DataTable)…

    Java 2023年5月30日
    078
  • Error parsing HTTP request header 控制台报错分析与解决

    控制台报错信息: java;gutter:true; org.apache.coyote.http11.AbstractHttp11Processor process 信息: Er…

    Java 2023年6月13日
    0104
  • 做仿牛客社区项目的搭建环境中,下载的合集包里面少个AOP

    在引入一批包的时候,即在start.spring.io网址中下载时,因为2022年start.spring.io更新后确实搜不到aop了,但是其他的包是可以的。这个工具的作用,就是…

    Java 2023年6月5日
    096
  • Spring Boot 面试问题

    说一说你对Spring Boot的理解 名词解释: Spring Boot 基于 Spring 开发, Spirng Boot 本身并 不提供 Spring 框架的核心特性以及扩展…

    Java 2023年6月7日
    076
  • vue.js和node.js的关系

    在学习vue的时候最先安装的就是node.js环境。那么没有node.js环境,vue.js能不能运行呢? 首先说一下node.js 就前端来说nodejs具有划时代的意义, 做前…

    Java 2023年6月5日
    073
  • Elasticsearch索引生命周期管理方案

    一、前言 在 Elasticsearch 的日常中,有很多如存储 系统日志、 行为数据等方面的应用场景,这些场景的特点是数据量非常大,并且随着时间的增长 &#x7D22;&…

    Java 2023年6月6日
    090
  • 【SSM框架】SpringMVC笔记 — 汇总

    1、什么是 SpringMVC? SpringMVC 是基于 MVC 开发模式的框架,用来优化控制器。它是 Spring 家族的一员,它也具备 IOC 和 AOP。 什么是MVC?…

    Java 2023年6月8日
    087
  • 如何使用原生的Feign

    什么是Feign Feign 是由 Netflix 团队开发的一款基于 Java 实现的 HTTP client,借鉴了 Retrofit、 JAXRS-2.0、WebSocket…

    Java 2023年6月14日
    088
  • idea使用教程-模板的使用

    一、代码模板是什么 它的原理就是配置一些常用代码字母缩写,在输入简写时可以出现你预定义的固定模式的代码,使得开发效率大大提高,同时也可以增加个性化。最简单的例子就是在Java中输入…

    Java 2023年6月5日
    073
  • 故事篇:终于给老婆讲明白什么是logback了!

    故事会迟到,但他从不会缺席。今天的故事开始了,你准备好了吗? 前奏 简单介绍一下我的老婆:集智慧与美貌于一身的女子——阿狸,一句”我们心有灵犀,不是吗?”让…

    Java 2023年6月5日
    098
  • 【Java面试手册-算法篇】给定一个数字,请判断是否为回文数字?

    在回答这个问题之前,首先得清楚什么是回文数字,回文数字有什么特征。 回文数字:设n是一任意自然数,若将n的各位数字反向排列所得自然数n1与n相等,则称n为一回文数。通俗地说,回文数…

    Java 2023年6月8日
    071
  • Session Cookie Token Json-Web-Token

    什么是认证(Authentication) 通俗地讲就是 验证当前用户的身份,证明”你是你自己”(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系…

    Java 2023年6月7日
    083
  • 设计模式 — Bridge(桥模式)

    桥模式(Bridge) 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化 如何应对这种多维度的变化?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至…

    Java 2023年6月16日
    0115
  • 【翻译】2 序列化过滤

    来源:Java官方文档 译者的话 由于译者的英文水平和编程水平都不高,不理解原文中的一些概念,一些句子也不知道如何翻译。对不知如何翻译的内容,译者使用了机器翻译,并在译文后面的括号…

    Java 2023年6月6日
    086
  • Elasticsearch笔记

    1 创建index,type : localhost:9200/get-together/group/1?pretty 然后跟上body: {“name”:…

    Java 2023年6月7日
    072
  • 记一次服务优化

    最近公司有个项目,再某一次活动时,流量暴增,达到了1万QPS,因数据库、服务扩容不及时,停止服务近5分钟,受影响用户近20W。所以有了这一次优化。 目标: 1、支持最大40000 …

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