Spring整合Mybatis原理

首先介绍一下Mybatis的工作原理
先简略的放两张图,后面的知识结合这两张图比较好理解
在这里插入图片描述

在这里插入图片描述

Mybatis的基本工作原理

在 Mybatis 中,我们可以使用⼀个接口去定义要执行sql,简化代码如下:
定义⼀个接口,@Select 表示要执行查询sql语句。

public interface UserMapper {
	@Select("select * from user where id = #{id}")
	User selectById(Integer id);
}

以下为执行sql代码:

InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 以下使我们需要关注的重点
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Integer id = 1;
User user = mapper.selectById(id);

Mybatis 的目的是:使得程序员能够以调用方法的方式执行某个指定的sql,将执行 sql 的底层逻辑进行了封装。
这里重点思考以下 mapper 这个对象,当调用 SqlSession 的 getMapper 方法时,会对传入的接口生成⼀个代理对象,而程序要真正用到的就是这个代理对象,在调用代理对象的方法时,Mybatis 会取出该方法所对应的sql语句,然后利用 JDBC 去执行 sql 语句,最终得到结果。

分析需要解决的问题

Spring 和 Mybatis 时,我们重点要关注的就是这个代理对象。因为整合的目的就是:把某个 Mapper 的代理对象作为⼀个 bean 放入 Spring 容器中,使得能够像使用⼀个普通 bean ⼀样去使用这个代理对象,比如能被 @Autowire 自动注入。
比如当 Spring 和 Mybatis 整合之后,我们就可以使用如下的代码来使用 Mybatis 中的代理对象了:

@Component
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUserById(Integer id) {
        return userMapper.selectById(id);
    }
}

UserService 中的 userMapper属性就会被自动注入为 Mybatis中 的代理对象。如果你基于⼀个已经完成整合的项目去调试即可发现,userMapper 的类型为:
org.apache.ibatis.binding.MapperProxy@41a0aa7d。证明确实是 Mybatis 中的代理对象。
那么现在要解决的问题的就是:
如何能够把 Mybatis的代理对象作为⼀个 bean 放入 Spring 容器中?
要解决这个,我们需要对 Spring 的 bean 生成过程有⼀个了解。

Spring 中 Bean 的产生过程

Spring 启动过程中,大致会经过如下步骤去生成bean

  1. 扫描指定的包路径下的 class 文件
  2. 根据 class 信息生成对应的 BeanDefinition
  3. 在此处,程序员可以利用某些机制去修改 BeanDefinition
  4. 根据 BeanDefinition 生成 bean 实例
  5. 把生成的 bean 实例放入 Spring 容器中

在 Spring 中,Bean 的产生过程可以分为以下几个步骤:

  1. 加载配置文件:Spring 容器会通过指定的配置文件(如XML文件、注解等)读取 Bean 的定义信息。
  2. 解析配置文件:Spring 容器根据配置文件的内容,找到所有需要创建的 Bean 的定义。
  3. 创建 Bean 的实例:Spring 容器根据 Bean 的定义信息,利用 Java 的反射机制创建Bean的实例,并调用其构造方法进行实例化。
  4. 设置 Bean 的属性:Spring 容器会根据配置文件中的属性值,通过调用 Bean 的 setter 方法来设置 Bean 的属性。
  5. 执行自定义的初始化方法:如果 Bean 实现了 InitializingBean 接口或在配置文件中通过 init-method 属性指定了初始化方法,Spring 容器会在 Bean 的所有属性设置完成后调用该方法进行初始化。
  6. 注入依赖:Spring 容器会根据配置文件中的依赖关系,自动将其他 Bean 注入到当前Bean中,可以通过构造器注入、setter 方法注入或字段注入等方式实现。
  7. 使用 Bean:经过以上步骤,Bean 已经成功创建并初始化,可以被其他对象引用和使用。
  8. 执行自定义的销毁方法:如果 Bean 实现了 DisposableBean 接口或在配置文件中通过 destroy-method 属性指定了销毁方法,当容器关闭时,Spring 容器会自动调用该方法对 Bean 进行销毁。
    以上是 Spring 中 Bean 的产生过程的简要介绍,通过 Spring 的 IoC(控制反转)和 DI(依赖注入)机制,开发人员可以更加便捷地管理和使用 Bean,提高代码的可维护性和灵活性。

注:需要更详细的可查阅相关资料!

讲这个问题,是想说明⼀个问题:在 Spring 中,bean 对象跟 class 没有直接关系,跟 BeanDefinition 才有直接关系。
回到我们要解决的问题:
如何能够把 Mybatis 的代理对象作为⼀个 bean 放入 Spring 容器中?
在 Spring 中,如果你想生成⼀个 bean,那么得先生成⼀个 BeanDefinition,就像你想 new⼀个对象实例,得先有⼀个 class。

解决问题

继续回到我们的问题,我们现在想自己生成⼀个 bean,那么得先生成⼀个 BeanDefinition,只要有了 BeanDefinition,通过在 BeanDefinition 中设置 bean 对象的类型,然后把 BeanDefinition 添加给 Spring,Spring 就会根据 BeanDefinition 自动帮我们生成⼀个类型对应的 bean 对象。
所以,现在我们要解决两个问题:
1. Mybatis 的代理对象的类型是什么?
因为我们要设置给 BeanDefinition
2. 我们怎么把 BeanDefinition 添加给 Spring 容器?
注意:上文中我们使用的 BeanFactory 后置处理器,他只能修改 BeanDefinition,并不能新增⼀个 BeanDefinition。我们应该使用Import 技术来添加⼀个 BeanDefinition。后文再详细介绍如果使用Import 技术来添加⼀个 BeanDefinition,可以先看⼀下伪代码实现思路。
假设:我们有⼀个UserMapper接口,他的代理对象的类型为UserMapperProxy。
那么我们的思路就是这样的,伪代码如下:

BeanDefinitoin beanDefinitoin = new BeanDefinitoin();
beanDefinitoin.setBeanClassName(UserMapperProxy.class.getName());
SpringContainer.addBd(beanDefinitoin );

但是,这里有⼀个严重的问题,就是上文中的 UserMapperProxy 是我们假设的,他表示⼀个代理类的类型,然而 Mybatis 中的代理对象是利用的 JDK 的动态代理技术实现的,也就是代理对象的代理类是动态生成的,我们根本无法确定代理对象的代理类到底是什么。
所以回到我们的问题:
Mybatis的代理对象的类型是什么?
本来可以有两个答案:

  1. 代理对象对应的代理类
  2. 代理对象对应的接口

那么答案1就相当于没有了,因为是代理类是动态生成的,那么我们来看答案2:代理对象对应的接口;
如果我们采用答案2,那么我们的思路就是:

BeanDefinition beanDefinition = new BeanDefinitoin();
// 注意这里,设置的是UserMapper
beanDefinition.setBeanClassName(UserMapper.class.getName());
SpringContainer.addBd(bdbeanDefinition);

但是,实际上给BeanDefinition对应的类型设置为⼀个接口是行不通的,因为Spring没有办法根据这个BeanDefinition去new出对应类型的实例,接口是没法直接new出实例的。
那么现在问题来了,要解决的问题:
Mybatis的代理对象的类型是什么?
两个答案都被我们否定了,所以这个问题是无解的,所以我们不能再沿着这个思路去思考了,只能回到最开始的问题:
如何能够把Mybatis的代理对象作为⼀个bean放入Spring容器中?
总结上⾯的推理:
我们想通过设置BeanDefinition的class类型,然后由Spring自动的帮助我们去生成对应的bean,但是这条路是行不通的。

终极解决方案

那么我们还有没有其他办法,可以去生成bean呢?并且生成bean的逻辑不能由Spring来帮我们做了,得由我们自己来做。

FactoryBean

有,那就是Spring中的FactoryBean。我们可以利用FactoryBean去自定义我们要生成的bean对象,比如:

@Component
public class LubanFactoryBean implements FactoryBean {
    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, newInvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (Object.class.equals(method.getDeclaringClass())) {
                    return method.invoke(this, args);
                } 
                else {
                    // 执行代理逻辑
                    return null;
                }
            }
        });
        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return UserMapper.class;
    }
}

我们定义了⼀个 LubanFactoryBean,它实现了 FactoryBean,getObject 方法就是用来自定义生成 bean
对象逻辑的。
执行如下代码:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println("lubanFactoryBean: " + context.getBean("lubanFactoryBean"));
        System.out.println("&lubanFactoryBean: " + context.getBean("&lubanFactoryBean"));
        System.out.println("lubanFactoryBean-class: " + context.getBean("lubanFactoryBean").getClass());
    }
}

将打印:
lubanFactoryBean: com.luban.util.LubanFactoryBeanKaTeX parse error: Expected 'EOF', got '&' at position 11: 1@4d41cee &̲lubanFactoryBea…Proxy20

从结果我们可以看到,从 Spring 容器中拿名字为"lubanFactoryBean"的bean对象,就是我们所自定义的 JDK 动态代理所生成的代理对象。

所以,我们可以通过 FactoryBean 来向 Spring 容器中添加⼀个自定义的 bean 对象。上文中所定义的 LubanFactoryBean 对应的就是UserMapper,表示我们定义了⼀个 LubanFactoryBean,相当于把 UserMapper 对应的代理对象作为⼀个 bean 放入到了容器中。

但是作为程序员,我们不可能每定义了⼀个 Mapper,还得去定义⼀个 LubanFactoryBean,这是很麻烦的事情,我们改造⼀下 LubanFactoryBean,让他变得更通用,比如:

@Component
public class LubanFactoryBean implements FactoryBean {
    // 注意这里
    private Class mapperInterface;

    public LubanFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBean.class.getClassLoader(),
                new Class[] { mapperInterface }, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        if (Object.class.equals(method.getDeclaringClass())) {
                            return method.invoke(this, args);
                        } else {
                            // 执行代理逻辑
                            return null;
                        }
                    }
                });

        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }
}

改造 LubanFactoryBean 之后,LubanFactoryBean 变得灵活了,可以在构造 LubanFactoryBean 时,通过构造传入不同的 Mapper 接口。

实际上 LubanFactoryBea 也是⼀个 Bean,我们也可以通过生成⼀个 BeanDefinition 来生成⼀个 LubanFactoryBean,并给构造方法的参数设置不同的值,比如伪代码如下:

BeanDefinition beanDefinition = new BeanDefinitoin();
// 注意⼀:设置的是LubanFactoryBean
beanDefinition .setBeanClassName(LubanFactoryBean.class.getName());
// 注意⼆:表示当前BeanDefinition在生成bean对象时,会通过调用LubanFactoryBean的构造方法来生成,并传入UserMapper
beanDefinition .getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class.getName())
SpringContainer.addBd(beanDefinition);

特别说⼀下注意⼆,表示表示当前 BeanDefinition 在生成 bean 对象时,会通过调用 LubanFactoryBean 的构造方法来生成,并传入 UserMapper 的 Class 对象。那么在生成 LubanFactoryBean 时就会生成⼀个 UserMapper 接口对应的代理对象作为 bean 了。

到此为止,其实就完成了我们要解决的问题:把 Mybatis 中的代理对象作为⼀个 bean 放入 Spring 容器中。只是我们这里是用简单的 JDK 代理对象模拟的 Mybatis 中的代理对象,如果有时间,我们完全可以调用 Mybatis 中提供的方法区生成⼀个代理对象。这里就不花时间去介绍了。

Import

到这里,我们还有⼀个事情没有做,就是怎么真正的定义⼀个 BeanDefinition,并把它添加到 Spring 中,上文说到我们要利用 Import 技术,比如可以这么实现:
定义如下类:

public class LubanImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        beanDefinition.setBeanClass(LubanFactoryBean.class);
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        // 添加beanDefinition
        registry.registerBeanDefinition("luban" + UserMapper.class.getSimpleName(), beanDefinition);
    }
}

并且在 AppConfig 上添加 @Import 注解:

@Import(LubanImportBeanDefinitionRegistrar.class)
public class AppConfig {

这样在启动Spring时就会新增⼀个BeanDefinition,该BeanDefinition会生成⼀个LubanFactoryBean对象,并且在生成LubanFactoryBean对象时会传入UserMapper.class对象,通过LubanFactoryBean内部的逻辑,相当于会自动生产⼀个UserMapper接口的代理对象作为⼀个bean。

总结

总结⼀下,通过我们的分析,我们要整合 Spring 和 Mybatis,需要我们做的事情如下:

  1. 定义⼀个 LubanFactoryBean
  2. 定义⼀个 LubanImportBeanDefinitionRegistrar
  3. 在 AppConfig 上添加⼀个注解 @Import(LubanImportBeanDefinitionRegistrar.class)

优化

这样就可以基本完成整合的需求了,当然还有两个点是可以优化的

优化一

单独再定义⼀个@LubanScan的注解,如下:

@Retention(RetentionPolicy.RUNTIME)
@Import(LubanImportBeanDefinitionRegistrar.class)
public @interface LubanScan {
}

这样在 AppConfig 上直接使用 @LubanScan 即可

优化二

在 LubanImportBeanDefinitionRegistrar 中,我们可以去扫描 Mapper,在
LubanImportBeanDefinitionRegistrar 我们可以通过 AnnotationMetadata 获取到对应的 @LubanScan 注解,所以我们可以在 @LubanScan 上设置⼀个value,用来指定待扫描的包路径。然后在 LubanImportBeanDefinitionRegistrar 中获取所设置的包路径,然后扫描该路径下的所有 Mapper,生成 BeanDefinition,放入 Spring容器中。
所以,到此为止,Spring整合Mybatis的核⼼原理就结束了,再次总结⼀下:

  1. 定义⼀个 LubanFactoryBean,用来将Mybatis的代理对象生成⼀个bean对象
  2. 定义⼀个 LubanImportBeanDefinitionRegistrar,用来生成不同Mapper对象的 LubanFactoryBean
  3. 定义⼀个 @LubanScan,用来在启动 Spring 时执行 LubanImportBeanDefinitionRegistrar的逻辑,并指定包路径

以上这个三个要素分别对象 org.mybatis.spring 中的:

  1. MapperFactoryBean
  2. MapperScannerRegistrar
  3. @MapperScan

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【UniApp开发小程序】”我的“界面实现+“信息修改“界面实现+登出账号实现+图片上传组件【基于若依管理系统开发】

文章目录 界面实现界面效果我的修改信息 “我的”界面实现api页面退出账号让自我介绍只显示一行&#xff0c;结尾多余的字使用...代替跳转到信息修改页面 信息修改界面实现api页面动态给对象设置属性名和值修改密码图片上传组件 部分后端代码Controller 界面实现 界面效果 我…

Windows11的VTK安装:VS201x+Qt5/Qt6 +VTK7.1/VTK9.2.6

需要提前安装好VS2017和VS2019和Qt VS开发控件以及Qt VS-addin。 注意Qt6.2.4只能跟VTK9.2.6联合编译&#xff08;目前VTK9和Qt6的相互支持版本&#xff09;。 首先下载VTK&#xff0c;需要下载源码和data&#xff1a; Download | VTKhttps://vtk.org/download/ 然后这两个文…

1 请使用js、css、html技术实现以下页面,表格内容根据查询条件动态变化。

1.1 创建css文件&#xff0c;用于编辑style 注意&#xff1a; 1.背景颜色用ppt的取色器来获取&#xff1a; 先点击ppt的形状轮廓&#xff0c;然后点击取色器&#xff0c;吸颜色&#xff0c;然后再点击形状轮廓的其他轮廓颜色&#xff0c;即可获取到对应颜色。 2.表格间的灰色线…

什么是搜索引擎?2023 年搜索引擎如何运作?

目录 什么是搜索引擎&#xff1f;搜索引擎的原理什么是搜索引擎爬取&#xff1f;什么是搜索引擎索引&#xff1f;什么是搜索引擎检索?什么是搜索引擎排序&#xff1f; 搜索引擎的目的是什么&#xff1f;搜索引擎如何赚钱&#xff1f;搜索引擎如何建立索引?网页抓取文本处理建…

【数字图像处理与应用】模板匹配

【数字图像处理与应用】模板匹配 题目模板匹配原理Matlab代码实现算法介绍显示图像的匹配结果 (最匹配的一个)MATLAB实现运行结果图像的相关值结果&#xff1a;在原图像上绘制检测到的目标位置&#xff1a;显示检测到的目标坐标&#xff1a; 显示图像的匹配结果 (最匹配的三个&…

聊聊spring-cloud的负载均衡

聊聊spring-cloud的负载均衡 1. 选择合适的负载均衡算法2. 合理设置超时时间3. 缓存服务实例列表4. 使用断路器5. 使用缓存Spring Cloud负载均衡组件对比RibbonLoadBalancerWebClient对比 总结 在微服务架构中&#xff0c;负载均衡是非常重要的一个环节&#xff0c;可以有效地提…

python与深度学习(六):CNN和手写数字识别二

目录 1. 说明2. 手写数字识别的CNN模型测试2.1 导入相关库2.2 加载数据和模型2.3 设置保存图片的路径2.4 加载图片2.5 图片预处理2.6 对图片进行预测2.7 显示图片 3. 完整代码和显示结果4. 多张图片进行测试的完整代码以及结果 1. 说明 本篇文章是对上篇文章训练的模型进行测试…

极速跳板机登陆服务器

目录 一&#xff1a;简单登陆跳板器二&#xff1a;一键申请相关的服务器权限三&#xff1a;简化登陆 一&#xff1a;简单登陆跳板器 登陆公司提供的网址&#xff0c; 下载自己的专属RSA密钥。在密钥文件处&#xff0c; 执行登陆指令&#xff1a; ssh -p 36000 -i id_rsa 用户跳…

LAXCUS分布式操作系统:人工智能最后一公里

随着人工智能技术的飞速发展&#xff0c;越来越多的应用场景开始涌现。然而&#xff0c;在实际应用中&#xff0c;人工智能技术仍然面临着许多挑战&#xff0c;其中最大的挑战之一就是如何实现人工智能的“最后一公里”。这一问题主要体现在以下几个方面&#xff1a; 计算资源…

程序员进阶之路:程序环境和预处理

目录 前言 程序的翻译环境和执行环境 翻译环境 运行环境 预处理&#xff08;预编译&#xff09; 预定义符号 #define #define 定义标识符 #define 定义宏 #define 替换规则 #和## #的作用 ##的作用 带副作用的宏参数 宏和函数对比 命名约定 #undef 命令行定义 条件…

Task :app:javaPreCompileDebug FAILED

一,报错内容 在打包react native项目的时候,报错如下信息,我的项目的react-native版本比较低,是0.62… > Task :app:javaPreCompileDebug FAILED Execution failed for task :app:javaPreCompileDebug. > Could not resolve all files for configuration :app:debugCom…

Windows下YUICompress实现js、css混淆压缩

首先&#xff0c;我们针对Linux下的部分命令进行Windows系统的对应实现 ls————cmd /c dir/b rm————cmd /c del mv————cmd /c move pwd————cmd /c chdir 注&#xff1a;cmd /c是执行完命令后关闭命令行窗口、cmd /k是执行完命令后不关闭命令行窗口、cmd /c sta…

关于计算机的各种编码

ASCII编码 ASCII (American Standard Code for Information Interchange)&#xff1a;美国信息交换标准代码是基于的一套电脑编码系统&#xff0c;主要用于显示现代英语和其他语言。它是最通用的标准&#xff0c;并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表…

如何在 SwiftUI 中使用 Touch ID 和 Face ID?

1. 需要通过指纹&#xff0c;面容认证后才能打开 App 2. 添加配置 需要向 Info.plist 文件中添加一个配置&#xff0c;向用户说明为什么要访问 添加 Privacy - Face ID Usage Description 并为其赋予值 $(PRODUCT_NAME) need Touch Id or Face ID permission for app lock 3. …

sql中group by 的使用

1、概述 Group By 从字面意义上理解就是根据By指定的规则对数据进行分组&#xff0c;所谓的分组就是将一个数据集划分为若干个小区域&#xff0c;然后针对若干个小区域进行数据处理 2、原始表 3、简单的Group By 示例1 select 类别&#xff0c;数量 as 数量之和 from A gro…

​MySQL高阶语句(三)

目录 1、内连接 2、左连接 3、右连接&#xff1a; 二、存储过程⭐⭐⭐ 4. 调用存储过程 5.查看存储过程 5.1 查看存储过程 5.2查看指定存储过程信息 三. 存储过程的参数 3.1存储过程的参数 3.2修改存储过程 四.删除存储过程 MySQL 的连接查询&#xff0c;通常都是将来…

(css)原生html实现遮罩层弹窗

(css)原生html实现遮罩层弹窗 效果&#xff1a; html <div class"overlay"><div class"content"><!-- 需要遮罩的内容 --> <el-table :data"tableData" size"mini" class"table-class" border stripe…

解决阿里云服务器不能访问端口

服务器已经下载了redis&#xff0c;kafka&#xff0c;但就是访问不了端口号&#xff0c; 开通云服务器以后&#xff0c;请一定在安全组设置规则&#xff0c;放行端口 防火墙要关闭

网络安全基础知识解析:了解常见的网络攻击类型、术语及其防范方法

目录 1、网络安全常识和术语 1.1资产 1.2网络安全 1.3漏洞 1.4 0day 1.5 1day 1.6后门 1.7exploit 1.8攻击 1.9安全策略 1.10安全机制 1.11社会工程学 2、为什么会出现网络安全问题&#xff1f; 2.1网络的脆弱性 2.4.1缓冲区溢出攻击原理&#xff1a; 2.4.2缓冲…

【简单认识MySQL函数和高级语句】

文章目录 一.常用查询1.按关键字排序&#xff08;ORDER BY 语句&#xff09;1、语法格式2、 ASC和DESC的排序概念3、举例1、按分数排序&#xff0c;默认不指定是升序排列2、分数按降序排列3、order by 还可以结合where进行条件过滤&#xff0c;筛选地址是南京的学生按分数降序排…