mybatis是如何集成到spring的之托管mapper接口

news2024/12/26 21:49:43

前言

mybatis集成到spring可以参考spring mvc集成mybatis进行数据库访问 ,其中mybatis集成到spring最重要的两个配置分别是SqlSessionFactoryBean和MapperScannerConfigurer,如下所示:

<!--mybatis sqlSeesionFactory配置-->
		<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
			<property name="dataSource" ref="dataSource" />
			<property name="configLocation" value="classpath:mybatis-config.xml" />
			<property name="mapperLocations" value="classpath:mapper/*Mapper.xml" />
			<property name="typeAliasesPackage" value="com.gameloft9.demo.dataaccess.model" />
		</bean>

		<!--mapper自动扫描配置-->
		<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
			<property name="basePackage" value="com.gameloft9.demo.dataaccess.dao" />
			<!-- 这里不要定义sqlSessionFactory, 定义了会导致properties文件无法加载 -->
		</bean>

在之前的文章mybatis是如何集成到spring的之SqlSessionFactoryBean 中我们已经对SqlSessionFactoryBean有了初步的探讨,接下来我们继续追问mapper接口是如何被spring管理起来的
我们平时使用mapper进行数据库操作时十分简单,首先定义好Dao层的接口:

**
 * 用户Mapper
 * Created by gameloft9 on 2017/11/28.
 */
public interface UserMapper {
    /**
    * 根据主键查询用户信息
    */
    UserTest selectByPrimaryKey(String id);
}

然后在repository层通过@Autowire就可以自动注入并使用了:
在这里插入图片描述
多么神奇的魔法!但我们需要对其一探究竟。我们先根据现有的知识提出几个问题,然后带着问题找答案:

1、我并没有定义任何UserMapper的Bean,它是怎么自动创建的?

MapperScannerConfigurer

MapperScannerConfigurer是专门用来扫描mapper接口并创建对应的spring bean。它的结构如下所示:
在这里插入图片描述
MapperScannerConfigurer实现了两个非常重要的接口BeanDefinitionRegistryPostProcessor和InitializingBean,其中InitializingBean我们已经很熟悉了。BeanDefinitionRegistryPostProcessor用的人比较少:

BeanDefinitionRegistryPostProcessor可以在bean被初始化前,让你修改applicationContext内部的BeanDefinition,加一些你想要的东西。

由此可见,MapperScannerConfigurer里面对某些BeanDefinition动了手脚,我们进去看看它做了什么:
在这里插入图片描述
看来MapperScannerConfigurer比较懒,它把工作丢给了ClassPathMapperScanner来做了。由于我们只配置了basePackage,所以被叉掉的部分不用去管它,重点是这个scan方法(basePackage可以是用分隔符分割的包,这里做了简单的分割。)在看scan方法前,我们先简单整理下流程:
在这里插入图片描述
MapperScannerConfigurer主要是读取需要扫描mapper的包路径,然后对其做一个简单的检查,然后提供一个可以对BeanDefinition做修改的入口,具体的修改工作交给了ClassPathMapperScanner。

ClassPathMapperScanner

好像ClassPathMapperScanner才是真正做苦力的那位老实人,在spring扫描完我们提供的包路径并创建完BeanDefinition后,ClassPathMapperScanner开始对BeanDefinition进行魔改。其中最重要的两点就是把mapper的className传给了构造函数以及篡改了bean的class为MapperFactoryBean。
在这里插入图片描述
在这里插入图片描述
在修改完Bean定义后,根据spring Bean的生命周期,肯定会创建Bean的实例。我们再看看修改这两个东西有什么用?

MapperFactoryBean

MapperFactoryBean的类结构如下:
在这里插入图片描述
实际上看到FactoryBean基本就可以断定mapper的代理bean是通过getObject创建的,实际上也确实如此:
在这里插入图片描述
对FactoryBean不熟悉的同学这里再简单提一下:

Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。FactoryBean跟普通Bean不同,其返回的对象不是指定类的一个实例,而是该FactoryBean的getObject方法所返回的对象。
一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。

看到getSqlSession().getMapper(this.mapperInterface)是不是很熟悉了?后面就正式进去到mybatis内部创建mapper代理对象的流程里去了,spring和mybatis的连接就建立起来了!
现在可以回答刚才的问题了,BeanDefinition修改了BeanClass为MapperFactoryBean.class,那么创建的时候是MapperFactoryBean的实例,然后因为添加了构造函数入参为原始的接口,所以原始的mapper的class也一并传递了进去,这样mapperFactoryBean的mapperInterface字段就保存着原始的mapper接口名称。
又由于MapperFactoryBean实现了FactoryBean接口,所以获取mapper实例的时候,实际调用的是getObject方法,把真正要创建的mapper接口传给getMapper方法,这样原始的mapper的代理对象就可以通过mybatis创建了。

举一反三

1、MapperFactoryBean继承SqlSessionDaoSupport有什么用?

MapperFactoryBean继承了SqlSessionDaoSupport实际上有好几个作用:
1、最重要的,是可以获取SqlSession对象,这样才可以创建mapper的代理对象
2、做一些简单的检查,例如mapperInterface不能为空

2、篡改BeanDefinition和复杂bean初始化的延伸思考

mapper bean的动态创建,通过扫描包并修改BeanDefinition,然后自定义的复杂的初始化逻辑通过实现InitializingBean+FactoryBean接口来达到目的。
作者曾经做过一个支付项目,支付渠道系统开放出来的远程dubbo服务就一个:

/**
* 微信渠道服务
*/
public interface WxDubboService {
   // 服务调用,所有服务都走它,包括下单,查询,退款等操作
   // jsonStr里面有具体的操作的msgType,根据它组装不通过服务处理链路
   String invoke(String jsonStr);
}

内部具体业务怎么处理全部通过一个msgType来区分,具体的业务服务配置大概长这个样子:

<bean id="refundService" class="com.xx.xx.xx.BusinessService">
		<property name="msgType">
			<util:constant static-field="com.xx.xx.wx.config.WXConstant$MsgType.REFUND" />
		</property>
		<property name="targetSys">
			<util:constant static-field="com.xx.xx.framework.config.Constant$TargetSys.WX" />
		</property>
		<property name="name" value="微信交易退款" />
		<property name="requestClass">
			<bean class="com.xx.xx.wx.jsonbean.RefundRequest" />
		</property>
		<property name="filterTemplate" ref="refundFilterTemplate" />
		<property name="businessHandler" ref="refundServiceHandler" />
	</bean>

然后服务的调用实现实际上是根据不同的msgType,然后通过注册中心拿到具体的BusinessService进行处理。这样的好处显而易见,内部子系统调用接口不需要新增,只需要新增msgType和对应的BusinessService即可,减少了接口数量。坏处也很明显,内部调用的逻辑复杂,必须知道msgType才知道最后走的是哪个调用。排查问题和理解难度都比普通dubbo服务要高。从本篇文章我们能有什么启发呢?
通过对比,我们可以大致看到和mybatis的逻辑是类似的,底层通过统一的服务来处理所有的逻辑,然后需要对外支持不同的接口。因此,我们可以类似的创建不同接口的Bean,然后通过FactoryBean将其实现丢给原来统一过的调用逻辑。
首先类似MapperScannerConfigurer我们配置一个ServiceScannerConfigurer:

     <!--service自动扫描配置-->
	<bean class="com.gameloft9.demo.mgrframework.mapper.ServiceScannerConfigurer">
		<property name="basePackage" value="com.gameloft9.demo.service.api.out" />
	</bean>

basePackage就是我们要提供的服务所在的package,我们在out package里面写一个测试接口:

/**
 * 微信订单服务
 */
public interface WxRefundService {
    // 退款
    String refund(String json);
}

剩下的就是一样的套路,我们在ServiceScannerConfigurer篡改BeanDefinition:

public class ServiceScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean {
    // api包
    private String basePackage;

    public void afterPropertiesSet() throws Exception {
        notNull(this.basePackage, "Property 'basePackage' is required");
    }

    // 扫描api包,并篡改BeanDefitnition
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        ClassPathServiceScanner scanner = new ClassPathServiceScanner(registry);
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }
public class ClassPathServiceScanner extends ClassPathBeanDefinitionScanner {
    // Service工厂Bean
    private ServiceFactoryBean<?> serviceFactoryBean = new ServiceFactoryBean<Object>();

    public ClassPathServiceScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {
            logger.warn("No service was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    // 篡改BeanDefinition 
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
            definition = (GenericBeanDefinition) holder.getBeanDefinition();

            // 保存原始interface
            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            // 篡改BeanClass为serviceFactoryBean
            definition.setBeanClass(this.serviceFactoryBean.getClass());
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }

    // 注册过滤器
    public void registerFilters() {
        boolean acceptAllInterfaces = true;

         // 支持接口
        if (acceptAllInterfaces) {
            // default include filter that accepts all classes
            addIncludeFilter(new TypeFilter() {
                public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                    return true;
                }
            });
        }

        // 去掉 package-info.java
        addExcludeFilter(new TypeFilter() {
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }

    // 保证接口可以注册BeanDefinition
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        } else {
            logger.warn("Skipping ServiceFactoryBean with name '" + beanName
                    + "' and '" + beanDefinition.getBeanClassName() + "' serviceInterface"
                    + ". Bean already defined with the same name!");
            return false;
        }
    }
    
    public ServiceFactoryBean<?> getServiceFactoryBean() {
        return serviceFactoryBean;
    }

    public void setServiceFactoryBean(ServiceFactoryBean<?> serviceFactoryBean) {
        this.serviceFactoryBean = serviceFactoryBean;
    }
}

然后在ServiceFactoryBean中创建代理类:

public class ServiceFactoryBean<T> implements FactoryBean<T>, ApplicationContextAware {

    /**
     * 要代理的接口类
     */
    private Class<T> serviceInterface;

    // 可以通过context获取其他spring bean
    private ApplicationContext context;

    public ServiceFactoryBean() {
    }

    public ServiceFactoryBean(Class<T> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }

    // 创建代理对象
    public T getObject() throws Exception {
        // 内部服务注册中心
        ServiceRegister serviceRegister = (ServiceRegister ) context.getBean("serviceRegister");
        
        // 不管是直接走动态代理还是调用其他处理逻辑,背后一定少不了动态代理的东西
        return (T)newProxyInstance(
                ServiceFactoryBean.class.getClassLoader(),
                new Class[] { serviceInterface },
                new ServiceInterceptor(serviceRegister));
    }

    public Class<?> getObjectType() {
        return this.serviceInterface;
    }

    public boolean isSingleton() {
        return true;
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       context = applicationContext;
    }

    // 代理
    private class ServiceInterceptor implements InvocationHandler{

        private ServiceRegister serviceRegister;

        public ServiceInterceptor(ServiceRegister serviceRegister){
            this.serviceRegister= serviceRegister;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
           // 请求参数
           JsonObject json = JSON.pareObject(args[0]);
          
            // 真正的服务处理
            BusinessService service = serviceRegister.get(json.get("msgType"));
            return service.invoke(args[0]);
        }
    }

    public Class<T> getServiceInterface() {
        return serviceInterface;
    }

    public void setServiceInterface(Class<T> serviceInterface) {
        this.serviceInterface = serviceInterface;
    }
}

ServiceRegister 可以理解为一个HashMap,里面根据msgType注册了具体的业务处理类。我们来写一个测试类:

@Slf4j
@ActiveProfiles("local")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class WxRefundServiceTest {

    // 自动注入
    @Autowired
    WxRefundService wxRefundService;

    @Test
    public void test(){
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setMerOrderId("xxxx");
        orderEntity.setAmount(1L);
        orderEntity.setMemo("付款备注");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("msgType","refund");
        jsonObject.put("order",orderEntity);
        String result = wxRefundService.refund(jsonObject.toString());
        log.info("{}",result);
    }
}

在这里插入图片描述
可以看到我们只是写了一个接口,却可以通过@Autuwired把bean给注入成功,而且调用wxRefundService.refund比调用wxDubboService .invoke更容易理解了。(这里省略了dubbo服务的发布改造)

总结

mybatis集成到spring最重要的两个配置分别是SqlSessionFactoryBean和MapperScannerConfigurer,其中MapperScannerConfigurer帮助我们把mapper对象托管到spring。使得我们没有定义任何Mapper的Bean,却可以通过@Autowired进行注入,大大简化了我们开发的难度,让我们把重心放在了sql逻辑本身。
这是一种非常好的设计思路,当我们日常工作中想把类托管到spring但又无法定义Bean或者不好定义Bean的时候,可以采取这种解决方案。

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

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

相关文章

实验五 串行通讯建模以及教程

目录 教程&#xff1a; 第一步下载matlib 第二步找到Simulink 相关文件 链接&#xff1a;https://pan.baidu.com/s/1Im-TUVfV4d8dok2ebXbmjw?pwd2222 提取码&#xff1a;2222 【实验目的】 1、了解MATLAB软件环境和Simulink建模过程&#xff0c;掌握Simulink图形化编程方…

给 compose draw 绘制的非规则图形添加点击监听

前言 导言 在之前的两篇文章中&#xff0c;我们从实例出发&#xff0c;以实践的方式简单介绍了 compose 自定义绘制&#xff08;如何自己绘制想要的控件&#xff09;、为自定义绘制增加动画&#xff08;让控件动起来&#xff09;。 在这篇文章中&#xff0c;我们依然从实例出…

Linux 权限-+完整思维导图+实图例子+深入细节+通俗易懂建议收藏

绪论 当时间的主人&#xff0c;命运的主宰&#xff0c;灵魂的舵手。上一回已将基础权限全部学习完了&#xff0c;本章开始我们将进入到权限的学习。 话不多说安全带系好&#xff0c;发车啦&#xff08;建议电脑观看&#xff09;。 附&#xff1a;红色&#xff0c;部分为重点部分…

基于Gitee的webhook编写hugo的自动构建实现博客自动更新

前言 差不多半年前趁着某云优惠&#xff0c;我买了5年的轻量级应用服务器。 拿着这个服务器原本打算做我的某个APP的服务端的&#xff0c;后来又觉得迁移数据好麻烦&#xff0c;所以随便搞了个博客上去。 选来选去&#xff0c;使用了 hugo 作为构建引擎。 正好&#xff0c;…

跟我一起使用 compose 做一个跨平台的黑白棋游戏(1)整体实现思路

前言 为什么写这系列文章 虽然 compose 正式版已经出来很久了&#xff0c;也有很多大佬写了很多教程文章和实例 demo &#xff0c;但是对于 compose 其实我也还是一知半解的。 特别是对于 compose 的状态管理&#xff0c;由于 compose 声明式的特性&#xff0c;如果不对状态…

chatgpt赋能Python-pythonfor怎么用

PythonFor SEO&#xff1a;如何利用Python提高SEO效果 SEO&#xff08;搜索引擎优化&#xff09;是现代数字营销中至关重要的一环。随着搜索引擎算法不断发展&#xff0c;优化网站以提高排名已经成为了一门复杂的艺术。幸运的是&#xff0c;Python提供了一些强大的工具来简化这…

chatgpt赋能Python-pythonelem

PythonELEM - 简易的Python学习工具 作为一名有10年Python编程经验的工程师&#xff0c;我可以深刻地体会到新手们学习Python的难处。PythonELEM是一个以Python为主题的学习工具&#xff0c;它可以帮助初学者更容易地掌握Python编程。 PythonELEM的功能 PythonELEM是一个简易…

餐饮油烟排放监测管理系统的设计与应用

安科瑞虞佳豪 连日来&#xff0c;河东区生态环境保护综合行政执法支队组织开展餐饮行业油烟净化专项检查工作&#xff0c;有效应对即将到来的夏季餐饮油烟对环境的污染&#xff0c;着力解决群众身边的环境问题。 执法人员对辖区餐饮商户集中区域开展常态化巡查&#xff0c;重…

Metal入门学习:绘制渲染三角形

一、编程指南PDF下载链接(中英文档&#xff09; 1、Metal编程指南PDF链接 https://github.com/dennie-lee/ios_tech_record/raw/main/Metal学习PDF/Metal 编程指南.pdf 2、Metal着色语言(Metal Shader Language:简称MSL)编程指南PDF链接 https://github.com/dennie-lee/ios_te…

chatgpt赋能Python-pythoncumsum

Python中的cumsum-累积求和函数 在数据处理中&#xff0c;经常需要对一个序列的元素进行累加。Python中提供了累积求和函数cumsum()&#xff0c;用于对一个序列的元素进行累加求和操作。 什么是cumsum()函数 cumsum()函数是Python中numpy模块中的一个函数&#xff0c;用于对…

通过小米万兆路由器将小米SoundMove 无缝接入 ChatGPT

通过小米万兆路由器将小米SoundMove 无缝接入 ChatGPT 本教程内容参考 Github 地址(可选)部署查看小米 SoundMove 信息的环境(可选)查看小米 SoundMove 的信息以容器方式部署程序到小米万兆路由器实际效果有待改善点 本教程内容 1 是记录了将小米 SoundMove 接入 ChatGPT 的操…

面向《海贼王》领域数据的知识图谱项目

访问【WRITE-BUG数字空间】_[内附完整源码和文档] 本次任务试图为《海贼王》中出现的各个实体&#xff0c;包括人物、地点、组织等&#xff0c;构建一个知识图谱&#xff0c;帮助我们更好的理解这部作品。 项目内容包括数据采集、知识存储、知识抽取、知识计算、知识应用五大部…

【运动规划算法项目实战】如何使用Pure Pursuit算法进行路径跟踪(附ROS C++代码)

文章目录 前言一、简介二、Pure Pursuit算法优缺点三、 代码实现3.1 算法实现步骤3.2 pure_pursuit.h3.3 pure_pursuit.cpp3.4 cubic_spline_path.py3.5 节点连接关系3.6 RVIZ显示四、总结前言 在自动驾驶和机器人导航领域,路径跟踪是一项关键技术,它使车辆或机器人能够按照…

多线程排序法

多线程排序法 chatGPT给我改的多线程排序法 using System.Collections.Concurrent; using System.Threading; ConcurrentBag<int> sortedList new ConcurrentBag<int>(); void Sort() { int[] arr {2, 6, 12, 8}; List<Thread> threads new List<Threa…

chatgpt赋能Python-pythondone

PythonDone&#xff1a;将Python编程变得更加简单 介绍 Python是一种有着广泛应用的高级编程语言&#xff0c;由于其简洁易学、开发效率高、可移植性好等特点&#xff0c;成为业内最热门的技术之一。但是&#xff0c;对于一些初学者来说&#xff0c;Python的学习过程可能还是…

【论文分享|SIGMOD‘22】WeTune 自动发现和验证重写规则

作者&#xff1a;谢其骏 北京航空航天大学在读硕士&#xff0c; Databend 研发工程师实习生 https://github.com/jun0315 论文原文&#xff1a; Zhaoguo Wang, Zhou Zhou, Yicun Yang, Haoran Ding, Gansen Hu, Ding Ding, Chuzhe Tang, Haibo Chen, Jinyang Li. WeTune: Auto…

【AIGC】11、MDETR | LeCun 团队于 2021 年推出的端到端多模态理解模型

文章目录 一、背景二、方法2.1 DETR2.2 MDETR 三、效果3.1 预训练调整后的检测器3.2 下游任务 论文&#xff1a;MDETR - Modulated Detection for End-to-End Multi-Modal Understanding 代码&#xff1a;https://github.com/ashkamath/mdetr 出处&#xff1a;ICCV 2021 Oral…

chatgpt赋能Python-pythonctrl快捷键

PythonCtrl快捷键使用指南 作为一名有10年Python编程经验的工程师&#xff0c;我深知PythonCtrl快捷键的重要性。PythonCtrl作为一个Python的开源编辑器&#xff0c;在每一个版本中都加入了更多的功能和快捷键&#xff0c;使得Python编程更加高效和易用。在本篇文章中&#xf…

卡方分布分析与应用

卡方检验(chi-square&#xff0c;记为χ2检验)是统计学中常用来计数数据分析的方法&#xff0c;对于总体的分布不作任何假设&#xff0c;因此它属于非参数检验法中的一种。本博文从理论到实际应用去阐述卡方检验&#xff0c;最后用python语言去实现卡方分布的代码。 1. 卡方分…

Spring Security的基本组件

一.简介 Spring Security通过一些列的过滤器完成了用户身份认证及其授权工作&#xff0c;每个过滤器都有不同分工&#xff0c;当然这些过滤器并不是全部都一起工作&#xff0c;而是根据我们需要什么功能&#xff0c;才会选取对应的过滤器加入。 当然这些过滤器并不是直接加入…