窥探系列之Mybatis-plus BaseMapper实现

news2024/11/15 8:01:40

我们知道,mybatisplus的BaseMapper接口中提供了一些如updateById的方法,框架本身已经实现了这些CRUD功能,基本的CRUD我们就没必要写sql,直接使用java语法就能对数据进行操控,很方便。那么这些功能是如何被实现的呢?这是我研读源码的动机
在这里插入图片描述

关键类

  1. AbstractSqlInjector及其子类DefaultSqlInjector
  2. AbstractMethod及其子类
  3. SqlMethod
  4. mapper代理:MybatisMapperProxy
  5. Configuration

MybatisMapperProxy 以代理类作为切入点

BaseMapper是一个接口,里面的方法都是抽象方法,所以很明显框架是通过代理的方式去实现这些抽象方法。

public class MybatisMapperProxy<T> implements InvocationHandler, Serializable {

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    ......省略
}

这段代码是来自 MyBatis 库中的 MapperProxy 类,用于为 Mapper 接口创建动态代理实例,通过该实例调用实际的 SQL 语句。

invoke 方法会在动态代理实例上调用,当 Mapper 接口中的方法被调用时,该方法将被执行。其中,proxy 参数是动态代理实例本身,method 参数是被调用的方法,args 参数包含传递给方法的参数。

第一个 if 语句检查方法是否属于 Object 类。如果是,则在 MapperProxy 实例本身上调用该方法,并将 args 数组作为参数传递。

第二个 else if 语句检查方法是否为默认方法,这是在 Java 8 中引入的。如果是默认方法,则使用 invokeDefaultMethod 方法调用它,传递 proxy、method 和 args 参数。

如果方法既不属于 Object 类也不是默认方法,则使用 method 参数从缓存中获取一个 MybatisMapperMethod 实例,并在其上调用 execute 方法,传递 sqlSession 和 args 参数。execute 方法负责使用 SqlSession 对象执行实际的 SQL 语句,并返回结果。

可以看到,BaseMapper的抽象方法的实现是在这两步:
final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);


MybatisMapperMethod#execute主要是一下逻辑
  1. 判断command类型,可以看到类型包括INSERT、UPDATE等;
  2. 转化参数;
  3. command.getName()作为入参执行sqlSession对应方法;
public class MybatisMapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MybatisMapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
            case INSERT: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.insert(command.getName(), param));
                break;
            }
            case UPDATE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.update(command.getName(), param));
                break;
            }
            case DELETE: {
                Object param = method.convertArgsToSqlCommandParam(args);
                result = rowCountResult(sqlSession.delete(command.getName(), param));
                break;
            }
            case SELECT:
......
    }
}

接下来,要看一下command的type和name具体是什么?

可以看到,将mapper的接口类和方法作为入参,调用resolveMappedStatement方法得到一个
MappedStatement 对象,取出id作为command的name,sqlCommandType作为command的type;

在resolveMappedStatement中,String statementId = mapperInterface.getName() + "." + methodName;将mapper的完全限定名和调用方法名拼接起来,作为statementId;

configuration.getMappedStatement(statementId);然后根据statementId从configuration对象中获取到对应的MappedStatement对象;

另外,这里引申出了一个关键的类com.baomidou.mybatisplus.core.MybatisConfiguration,很显然MybatisConfiguration对象维护了MappedStatement对象集合,所以要想弄明白对应sqlCommandType,需要研究MappedStatement对象如何被加载到MybatisConfiguration中;

  public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
# 类:org.apache.ibatis.binding.MapperMethod
    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      String statementId = mapperInterface.getName() + "." + methodName;
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }

以insert为例,继续往下看sqlSession.insert做了哪些操作?

  1. 根据statementId,从configuration对象中获取到对应的MappedStatement,然后调用执行器执行MappedStatement对象;
  2. MybatisSimpleExecutor#doUpdate 中调用configuration对象的newStatementHandler方法创建一个StatementHandler 对象;
  3. prepareStatement(handler, ms.getStatementLog(), false) 生成PreparedStatement对象;
  4. ps.execute()执行sql;
# org.apache.ibatis.session.defaults.DefaultSqlSession

  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }
 
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
public class MybatisSimpleExecutor extends AbstractBaseExecutor {

    public MybatisSimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }


    @Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
            stmt = prepareStatement(handler, ms.getStatementLog(), false);
            return stmt == null ? 0 : handler.update(stmt);
        } finally {
            closeStatement(stmt);
        }
    }
    
    private Statement prepareStatement(StatementHandler handler, Log statementLog, boolean isCursor) throws SQLException {
        Statement stmt;
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        //游标不支持返回null.
        if (stmt == null && !isCursor) {
            return null;
        } else {
            handler.parameterize(stmt);
            return stmt;
        }
    }
    
......

public class PreparedStatementHandler extends BaseStatementHandler {

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
......


PreparedStatement对象的创建

  1. boundSql.getSql()获取sql模版;
  2. connection.prepareStatement创建PreparedStatement对象;
  3. BaseStatementHandler#BaseStatementHandler中,获取BoundSql对象;
# org.apache.ibatis.executor.statement.PreparedStatementHandler

  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }
# org.apache.ibatis.executor.statement.BaseStatementHandler

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

注入抽象方法

前提:
mybatis的mapper类不能有重载方法Mapper类中存在名称相同的方法重载报错,所以生成的MappedStatement的id只由完全类限定名 + 方法名构成。

  1. com.baomidou.mybatisplus.core.injector.AbstractSqlInjector#inspectInject
  2. com.baomidou.mybatisplus.core.injector.AbstractMethod#injectMappedStatement

以deleteById为例,

public class DeleteById extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        String sql;
        SqlMethod sqlMethod = SqlMethod.LOGIC_DELETE_BY_ID;
        if (tableInfo.isLogicDelete()) {
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), sqlLogicSet(tableInfo),
                tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
                tableInfo.getLogicDeleteSql(true, false));
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return addUpdateMappedStatement(mapperClass, modelClass, sqlMethod.getMethod(), sqlSource);
        } else {
            sqlMethod = SqlMethod.DELETE_BY_ID;
            sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(), tableInfo.getKeyColumn(),
                tableInfo.getKeyProperty());
            SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, Object.class);
            return this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/673474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【并发知识点】AQS的实现原理及应用

系列文章目录 AQS的实现原理及应用 CAS的实现原理及应用 文章目录 系列文章目录前言一、AQS是什么&#xff1f;1、应用场景2、优缺点 二、案例应用1.使用AQS来实现一个简单的互斥锁2.模拟赛龙舟程序 总结 前言 在Java技术方面&#xff0c;AQS指的是AbstractQueuedSynchronize…

2023最新高薪岗位大爆料,大模型算法工程师!凭什么人均月薪50K

大模型算法工程师工资收入一般多少钱一个月&#xff1f; 最多人拿50K以上占 53.7%&#xff0c;2023年较2022年增长了10%。 按学历统计&#xff0c;本科工资&#xffe5;41.9K。 按经验&#xff0c;1-3年工资&#xffe5;40.0K。 一起来看华为招聘的大模型工程师的工资水准 岗位…

[补充]机器学习实战|第二周|第2章:监督学习|课后习题

目录 第二章 监督学习 2. 使用不同的超参数&#xff0c;如kernel"linear"和kernel“rbf”&#xff0c;尝试一个支持向量机回归器。并思考最好的SVR预测器是如何工作的&#xff1f; [代码]3. 为MNIST数据集构建一个分类器&#xff0c;并在测试集上达成超过97%的精度…

关于Java中单例模式(饿汉模式和懒汉模式)的简析

目录 一.什么是单例模式 二.饿汉模式和懒汉模式 饿汉模式 代码 懒汉模式 代码 关于多线程安全的问题 如何解决懒汉模式多线程安全问题 双if判断 一.什么是单例模式 简单来说,就是我们在程序中通过代码进行限制,在该程序中 只能创建一个对象 二.饿汉模式和懒汉模式 …

【2023,学点儿新Java-17】变量与运算符:Java中的关键字及类型划分(附: 官网) | 保留字 | 字面量 | 附:Java部分关键字介绍

前情回顾&#xff1a; 【2023&#xff0c;学点儿新Java-16】编程语言的学习方法总结 | 编程的本质和架构 | 如何深度理解编程知识和技能 | 如何成为优秀的软件开发工程师 | 附&#xff1a;Java初学者的困惑&#xff01;【2023&#xff0c;学点儿新Java-15】案例分享&#xff1…

机器视觉初步7:模板匹配专题

今天端午&#xff0c;祝各位端午安康&#xff01; 今天来说说模板匹配这个专题。 模板匹配&#xff08;Template Matching&#xff09;是一种图像处理技术&#xff0c;用于在一幅图像上查找与另一幅模板图像相同的区域。模板图像和待匹配图像的大小相同。模板匹配的目的是在待…

【MongoDB大作业】MongoDB服务器的部署

【MongoDB大作业】MongoDB服务器的部署 作业要求作业步骤一、在VMware Workstations安装Linux操作系统&#xff08;最小安装即可&#xff09;二、安装完成后登录系统三、将ip地址设置为固定ip地址192.168.80.134四、设置虚拟网络编辑器五、使用 CRT 工具远程连接虚拟机六、下载…

《项目实战》构建SpringCloud alibaba项目(一、构建父工程、公共库、网关))

系列文章目录 构建SpringCloud alibaba项目&#xff08;一、构建父工程、公共库、网关&#xff09; 构建SpringCloud alibaba项目&#xff08;二、构建微服务鉴权子工程store-authority-service&#xff09; 文章目录 系列文章目录1、概要2、整体架构流程2.1、技术结构组成部分…

非监督学习

聚类Clustering 查看大量数据点&#xff0c;自动找到彼此相关或相似的数据点 K-means算法 原理 1.随机选择点&#xff0c;找聚类的中心位置。将点分配给簇质心 2.移动簇质心 不断重复这两个步骤 优化目标 成本函数失真函数distortion 在每次迭代中&#xff0c;失真成本…

极致呈现系列之:Echarts旭日图的绚丽奇观

目录 什么是旭日图旭日图的特性及应用场景旭日图的特性应用场景 旭日图常用的配置项创建基本的旭日图自定义旭日图样式样式旭日图的高级应用 什么是旭日图 旭日图是一种可视化图表&#xff0c;用于展示层级结构和层级之间的关系。它以一个圆形为基础&#xff0c;由多层的环形图…

【从零开始学习JAVA | 第七篇】API 简介

目录 前言 API介绍&#xff1a; 总结&#xff1a; 前言 这篇章为前导性文章&#xff0c;主要向大家介绍了什么是API&#xff0c;不要求掌握&#xff0c;感兴趣的小伙伴们可以看一看。 API介绍&#xff1a; API&#xff08;Application Programming Interface&#xff09;是指…

webpack原理之开发第一个loader

一. 搭建项目结构 整体项目结构如图&#xff1a; 1. 初始化包管理器package.json npm init -y 2. 打包入口文件src/main.js 3. 单页面入口public/index.html 4. 配置webpack.config.js const path require(path) const HtmlWebpackPlugin require("html-webpack-plu…

ChatBot聊天机器人学习1

1、Bot定义 能执行大量自动化、高速或机械式、繁琐的工作的计算机程序&#xff0c;包括但不仅限于聊天功能 2、Retrieval-based KE&#xff08;知识网络&#xff09;基于信息的提取。&#xff08;检索的过程中有延迟&#xff0c;设置比较快捷的检索方式&#xff09; 2.1 Int…

一看就懂的gulp操作指南:让前端工作变得更加轻松

文章目录 I. 简介什么是gulp为什么要使用gulp安装gulp II. Gulp入门任务&#xff08;task&#xff09;和流&#xff08;stream&#xff09;的概念使用gulp来处理文件基本的gulp任务&#xff08;拷贝文件、压缩文件、编译Sass等&#xff09; III. Gulp进阶使用插件开发面向生产的…

基于Python+tensorflow深度学习VGG-19图像风格迁移+自动去噪(MNIST数据集)机器学习+人工智能+神经网络——含全部Python工程源码

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境TensorFlow 环境 模块实现1. 图片处理2. 模型构造 系统测试工程源代码下载其它资料下载 前言 本项目基于 MNIST 数据集&#xff0c;使用 VGG-19 网络模型&#xff0c;将图像进行风格迁移&#xff0c;实现去噪功…

数字信号处理课程设计——调制与解调

文字目录 数字信号处理课程设计 摘要&#xff1a; 1绪论 1.1通信信号的调制与解调 1.2设计题目 2卷积定理和希尔伯特公式理论推导 2.1卷积定理 ​2.2希尔伯特公式 3信号DSB调制与希尔伯特解调 3.1过程框图 3.2相关理论推导 3.2.1卷积定理在调制中的应用 3.2.2希尔…

某马 qiankun 公开课 学习记录

端午早晨阳光正好&#xff0c;起来学习一小下 客观评价一哈&#xff1a;此视频适合不了解 qiankun 的朋友入门观看&#xff0c;更详细的使用方法还是推荐 qiankun 官网哦&#xff0c;老师讲的生动活泼&#xff0c;值得萌新一听 某马 qiankun 公开课 - bilibili ovo很多公司的…

高通Camera Log Debug 知识点

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、Camx UMD Log Debug二、Camx KMD log Debug三、常用缩写解释四、参考文献 一、Camx UMD Log Debug 1.1 两种方式设置camx UMD Log /vendor/etc/cam…

GPT-3.5眼中的编程语言之最:Python的卓越之处

当谈论编程语言的选择时&#xff0c;每个开发者都有自己的偏好和理由。作为GPT-3.5&#xff0c;以我的分析和学习能力&#xff0c;我也有自己心目中的编程语言之最。在众多编程语言中&#xff0c;Python在我的眼中独树一帜&#xff0c;是最令人着迷和受欢迎的编程语言之一。 首…

面试经典150题(1)

文章目录 前言除自身以外数组的乘积要求思路代码 跳跃游戏|要求题解代码 跳跃游戏||要求题解代码 前言 今天开始我将陆续为大家更新面试经典150题中较难理解的题目。今天我为大家分享的是&#xff0c;除自身以外数组的乘积、跳跃游戏| 和 跳跃游戏||。 除自身以外数组的乘积 …