mybatis-plus 源码解析

news2024/11/26 8:22:55

没错,又是需求导致我 需要研究下 mybatis-plus了。。。。

本来我想直接网上百度出来一篇,看看得了,就不自己从头研究了

我都看了一遍,但是很可惜 ,没一个能用的。。。。

有一个掘金的写的,我看了下他总共写了5篇,借用下 里面的一个评论

百度不到,就自己搞一个吧

我的期望

开始

目前mybatis-plus 最新的代码 在 v3.5.3.1

因为我们自己组件库 用的是 3.4.0 所以这次 也用的 3.4.0

之前有人私聊问,idea 找不到 tag分支

mybatis-plus 基础使用

一、环境准备: 1.1、导入依赖 将springboot 整合mybatis的依赖替换为整合mybatis-plus的起步依赖;

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.0</version>
    </dependency>

IDEA按照一个插件Maven Helper可以查看是否有依赖冲突。

1.2、修改配置文件 端口、数据库、mybatis-plus日志输出、驼峰映射、xml位置等

server:
  port: 8889
spring:
  datasource:
    url: jdbc:mysql://localhost:3308/boot_mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True
    username: root
    password: root

mybatis-plus:
  mapper-locations: mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true
  type-aliases-package: com.example.demo.entity

1.3、IUser实体类 绑定表名、主键名

@Data
@TableName("t_user")
public class IUser {
    /**
     * 指定主键名、主键生产策略
     */
    @TableId(value = "u_id", type = IdType.AUTO)
    private Integer uId;
    /**
     *指定列名,若一致可以不用指定 
     */
    @TableField("user_name")
    private String userName;

    private String email;
    private String passWord;
    private Date birth;
    private int gender;
}

1.4、UserMapper 只要继承BaseMapper<~>即可、其他方法和xml可以删掉。

@Mapper
public interface IUserMapper extends BaseMapper<IUser> {}

IUserMapper继承BaseMapper<~>后就可以不用编写XML来实现了

mybatis-plus debug

首先 mybatis-plus 是基于 mybatis的,这个没毛病

我们首先从 META-INF 中 spring.factories 看初始化了什么

# Auto Configure
org.springframework.boot.env.EnvironmentPostProcessor=\
  com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

SpringBoot常用接口–EnvironmentPostProcessor

一般用于读取环境变量达到多个微服务共同配置的修改与维护。当我们有多套环境(开发、测试、生产等等)时,每套环境都有专属的配置文件存放于配置中心(以nacos为例),可能存放于不同的配置中心(每个环境有专属的配置中心,服务地址不同),也可能存放于同一nacos的不同命名空间,也或者同一命名空间的不同分组等等。同一套代码在不同环境运行需要不同的配置文件,这时,我们就可以在项目启动时,实现EnvironmentPostProcessor接口,在postProcessEnvironment方法中读取环境变量或者启动命令参数,从而获取本环境下nacos的服务地址,或命名空间名称、分组名称等等,然后就可以根据获取的配置参数或环境变量来读取不同的配置文件,从而实现不同环境使用不同的配置文件,不用修改代码或者本地配置文件。

SafetyEncryptProcessor 是安全加密处理器

这不是我们这次的重点 忽略,抓住重点

MybatisPlusLanguageDriverAutoConfiguration

这个里面 看起来也就是 初始化

mybatis-freemarker 1.2.x
mybatis-velocity
Thymeleaf

...看起来 也不是重点啊,无奈

小知识点: 如果想 必须引用了 xxx 才能触发某个类

使用

@ConditionalOnClass(LanguageDriver.class)

MybatisPlusAutoConfiguration

看名字 靠点谱了。。。

 If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
* configuration file is specified as a property, those will be considered,
* otherwise this auto-configuration will attempt to register mappers based on
* the interface definitions in or under the root auto-configuration package.

嗯嗯 注释说的是

如果{@link org.mybatis.spring.annotation。使用MapperScan},或者将配置文件指定为属性,这些都将被考虑,否则这个自动配置将尝试根据根自动配置包中或下面的接口定义注册映射器。

@ConditionalOnSingleCandidate表示当指定Bean在容器中只有一个,或者虽然有多个但是指定首选Bean。

@AutoConfigureAfter 在加载配置的类之后再加载当前类

public class MybatisPlusAutoConfiguration implements InitializingBean {

}

public interface InitializingBean {

   void afterPropertiesSet() throws Exception;

}

因为实现了 InitializingBean 接口,我们当然要看 afterPropertiesSet 方法

@Override
public void afterPropertiesSet() {
    if (!CollectionUtils.isEmpty(mybatisPlusPropertiesCustomizers)) {
        mybatisPlusPropertiesCustomizers.forEach(i -> i.customize(properties));
    }
    checkConfigFileExists();
}

debug下看看

行吧,到这 ,基本可以看出 mybatis-plus-boot-starter 的这几个类 都是 针对 springboot的一些配置,和它本身的 扫描 / 动态代理都没有关系,我们应该往依赖的jar 看看

使用的是 gradle

那 BaseMapper的动态代理 应该也在 mybatis-plus-core中啊,去看看

spring-devtools.properties 干什么的

如果有一个多模块项目,只有部分导入到你的IDE中,你可能需要自定义一下。首先创建一个文件:META-INF/spring-devtools.properties。该文件中,可以有以前缀 restart.exclude. 和 restart.include. 开头的属性。前者会被放入base类加载器,后者则被放入restart类加载器。

转折点

这按照套路走,也找不到啊,行吧,我们都知道 肯定用到了 动态代理,我就find newProxyInstance 方法不就行了

public class MybatisMapperProxyFactory<T> {

    @Getter
    private final Class<T> mapperInterface;
    @Getter
    private final Map<Method, MybatisMapperProxy.MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

    public MybatisMapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MybatisMapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        //接口代理类 真正执行的逻辑在这
        final MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy<>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }
}

public class MybatisMapperProxy<T> implements InvocationHandler, Serializable {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        if (Object.class.equals(method.getDeclaringClass())) {
            //可能method 不是 BaseMapper的 就走原来的逻辑
            return method.invoke(this, args);
        } else {
            //是 baseMapper的方法 就要统一处理
            return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
}

}

//真正执行 业务逻辑 
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case INSERT: {
           xxx
            break;
        }
        case UPDATE: {
            xxx
            break;
        }
        case DELETE: {
           xxx
            break;
        }
        case SELECT:
            xxx
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
        throw new BindingException("Mapper method '" + command.getName()
            + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
}

其实这个直接copy 过去就能实现接口的 动态代理

MybatisMapperProxy invoke 我想看怎么做的动态代理,但是断点进不去

想进invoke 方法 需要有人调用 接口方法 ,才能触发,低级错误

小总结

现在 后面2步都可以了,就剩第一步 @MetadataScan 扫描指定的包路径了,这个属于mybatis 的范围了

@MetadataScan 扫描指定的包路径

在 mybatis-spring 中

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) {
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
  }
}

要想 进行我们的扫描,需要一些自定义的操作

效果

这是改动前

小插曲

两种获取方式 ,返回值还不对。。。哎 可能是 key 我设置的不对

我现在是

BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(beanClazz);

GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition();

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClazz);

definition.setBeanClass(ServiceFactory.class);

definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
//beanClazz.getSimpleName() = TestMapper
registry.registerBeanDefinition(beanClazz.getSimpleName(), definition);

这个就 涉及@Resource 是怎么获取值的

需要跟进到 其中的代码,看看其区别

TestMapper a1 = ApplicationContextUtil.getBean(TestMapper.class);
Object a2 = ApplicationContextUtil.getBean("TestMapper");

ApplicationContextUtil.getBean(TestMapper.class); 关键代码如下

这代表 我们赋值的时候 应该 也用这种全路径

小问题

Object 接就好使

我用下面这种就不存在

这是因为我的代理返回的对象 接口class 错误

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

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

相关文章

备战蓝桥杯【一维前缀和】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…

如何使用Arduino IDE编程ATTINY13/ATTINY13A单片机

尽管ATtiny系列被认为是非常便宜和有用的&#xff0c;但是仍然缺少有关它的项目和教程。在本篇文章中&#xff0c;您将学习如何使用通过Arduino IDE编程的ATtiny13微控制器开始构建应用程序。 首先&#xff0c;ATtiny13是基于AVR增强RISC架构的低功耗CMOS 8位微控制器。通过在一…

Seata-Server分布式事务原理加源码 (四)- Seata事务日志储存方式DB

Seata Server&#xff08;TC&#xff09;环境搭建详解 Server端存储模式&#xff08;store.mode&#xff09;支持三种&#xff1a; file&#xff1a;单机模式&#xff0c;全局事务会话信息内存中读写并持久化本地文件root.data&#xff0c;性能较高&#xff08;默认&#xff…

【Redis】Redis持久化之RDB详解(Redis专栏启动)

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建工设优化。文章内容兼具广度深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于知名金融公…

【Unity VR开发】结合VRTK4.0:创建一个门

语录&#xff1a; 独有英雄驱虎豹&#xff0c;更无豪杰怕熊罴 前言&#xff1a; 在我们做项目时&#xff0c;会遇到需要打开门&#xff0c;或者柜子的门&#xff0c;今天&#xff0c;我们就来学习一下关于门的控件。以模拟可以打开和关闭的门。我们将使用角度驱动器来创建此门…

大火的ChatGPT能为自动驾驶带来什么?

/导读/最近的科技圈&#xff0c;大家都被微软推出的ChatGPT刷屏&#xff0c;作为工智能公司OpenAI于2022年11月推出的聊天机器人&#xff0c;其能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;甚至能完成撰写邮件、视频脚本、文案…

MySql8.0 安全卸载

1. 停止MySQL服务 winR 打开运行&#xff0c;输入 services.msc 点击 “确定” 调出系统服务。 2. 卸载MySQL相关组件 打开控制面板 —> 卸载程序 —> 卸载MySQL相关所有组件 3. 删除MySQL安装目录 4. 删除MySQL数据目录 数据存放目录是在 C:\ProgramData\MySQL&#…

跳转语句与数组,函数

1.break语句作用: 用于跳出选择结构或者循环结构break使用的时机:出现在switch条件语句中,作用是终止case并跳出switch出现在循环语句中,作用是跳出当前的循环语句出现在嵌套循环中&#xff0c;跳出最近的内层循环语句2.continue语句作用 : 在循环语句中&#xff0c;跳过本次循…

vue项目第四天

使用elementui tabplane组件实现历史访问记录组件的二次封装<el-tabs type"border-card"><el-tab-pane label"用户管理">用户管理</el-tab-pane><el-tab-pane label"配置管理">配置管理</el-tab-pane><el-tab-…

如何量测太阳光模拟器的光谱致合度?

太阳模拟器是根据国际法规JIS、IEC60904、美国材料试验协会开发设计的AAA级太阳模拟器。对于100毫米100毫米和200毫米200毫米的光斑尺寸&#xff0c;光斑强度的输出功率范围可以从0.1到1太阳光强度。此外&#xff0c;还提供了灵活的出光方向&#xff0c;以满足用户的研究需求&a…

JavaWeb_JSP

目录 一、概述 二、入门 1.创建一个maven的web项目 2.搭建环境 3.创建jsp页面 4.编写代码 5.测试 三、JSP原理 四、JSP脚本 五、JSP缺点 六、EL表达式 1.概述 2.代码演示 3.域对象 七、JSTL标签 1.概述 2.if标签 3.forEach标签 八、MVC模式和三层架构 1.MVC…

Linux驱动学习环境搭建

背景常识 一、程序分类 程序按其运行环境分为&#xff1a; 1. 裸机程序&#xff1a;直接运行在对应硬件上的程序 2. 应用程序&#xff1a;只能运行在对应操作系统上的程序 二、计算机系统的层次结构 所有智能设备其实都是计算机&#xff0c;机顶盒、路由器、冰箱、洗衣机、汽…

线程安全的集合类

1.多线程环境使用 ArrayList 1.自己使用同步机制 (synchronized 或者 ReentrantLock) 2.Collections.synchronizedList(new ArrayList); synchronizedList 是标准库提供的一个基于 synchronized 进行线程同步的 List.synchronizedList 的关键操作上都带有 synchronized 3…

Linux多版本python切换以及多版本pip对应 (cloud studio Ubuntu16.04)

linux && cloud studio && Ubuntu16.04 简单解决多版本python切换以及多版本pip对应问题 1.python2切换成python 多版本python: 更改前先查看版本号 $ python -V Python 2.7.12 $ python2 -V Python 2.7.12 $ python3 -V Python 3.5.2 通过下面的命令看到py…

在传染病中,肠道微生物-免疫力-营养在优化治疗策略中的作用

谷禾健康 传染病&#xff0c;肠道微生物&#xff0c;营养 传染病和感染目前是许多地区尤其是低收入国家主要死亡原因&#xff0c;也是婴儿和老年人等弱势群体的主要风险。免疫系统在这些感染的易感性、持续性和清除中起着至关重要的作用。由于 70-80% 的免疫细胞存在于肠道中&a…

详解子网技术

一 : Internet地址 Intemet实质上是把分布在世界各地的各种网络如计算机局域网和广域网、数字数据通信网以及公用电话交换网等互相连接起来而形成的超级网络。但是 , 网络的物理地址给Internet统一全网地址带来两个方面的问题: 第一&#xff0c;物理地址是物理网络技术的一种…

postman-请求前参数预处理(pre-request)

文章目录一、Pre-request Scrip的简介二、 变量2.1环境变量2.2全局变量2.3动态变量&#xff08;内置变量&#xff09;2.4数据变量三、全局变量的定义和使用3.1全局变量的定义3.2全局变量的使用四、动态变量的使用4.1通过界面操作完成Gd变量使用4.2在脚本区写代码调用Pre-reques…

分享113个HTML电子商务模板,总有一款适合您

分享113个HTML电子商务模板&#xff0c;总有一款适合您 113个HTML电子商务模板下载链接&#xff1a;https://pan.baidu.com/s/1JIlnB8qpg4wIuh-fi0e-Bg?pwdiwvr 提取码&#xff1a;iwvr Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 import os import shuti…

Elasticsearch和Solr的区别

背景&#xff1a;它们都是基于Lucene搜索服务器基础之上开发&#xff0c;一款优秀的&#xff0c;高性能的企业级搜索服务器。&#xff08;是因为他们都是基于分词技术构建的倒排索引的方式进行查询&#xff09;开发语言&#xff1a;java语言开发诞生时间&#xff1a;Solr2004年…

CAP和BASE理论

CAP理论CAP是 Consistency、Availability、Partition tolerance 三个词语的缩写&#xff0c;分别表示一致性、可用性、分区容忍性。它指出一个分布式计算系统不可能同时满足以下三点&#xff1a;• 一致性&#xff08;Consistency&#xff09; &#xff1a;等同于所有节点访问同…