死磕MybatisPlus系列:Mapper的奇妙之旅

Mybatis Plus源码解析系列篇之Mapper的奇妙之旅

一、MybatisPlus初体验

MybatisPlus是一个基于mybatis的开源orm框架,其内置的Mapper、Service让开发者仅需简单的配置,就能获得强大的CRUD能力;其强大的条件构造器,可以满足各类需求。所以越来越多的开发者使用MybatisPlus来替代基础的Mybatis。

和SpringBoot的集成

MybatisPlus可以无缝集成在SpringBoot中,常用方式如下:

1、引入MybatisPlus依赖

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

2、定义一个数据库实体

package cn.javayuli.demo.entity;import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;/*** 实体类* @author GuiLin Han* @since 1.0.0*/
@Data
@TableName("t_java_coder")
public class JavaCoder {private String name;private Integer age;
}

3、定义一个Mapper接口

package cn.javayuli.demo.mapper;import cn.javayuli.demo.entity.JavaCoder;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;/*** @author GuiLin Han* @since 1.0.0*/
public interface JavaCoderMapper extends BaseMapper<JavaCoder> {
}

4、定义一个Service和实现类

package cn.javayuli.demo.service;import cn.javayuli.demo.entity.JavaCoder;
import com.baomidou.mybatisplus.extension.service.IService;/*** @author GuiLin Han* @since 1.0.0*/
public interface JavaCoderService extends IService<JavaCoder> {
}
package cn.javayuli.demo.service.impl;import cn.javayuli.demo.entity.JavaCoder;
import cn.javayuli.demo.mapper.JavaCoderMapper;
import cn.javayuli.demo.service.JavaCoderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;/*** @author GuiLin Han* @since 1.0.0*/
@Service
public class JavaCoderServiceImpl extends ServiceImpl<JavaCoderMapper, JavaCoder> implements JavaCoderService {
}

5、提供一个Controller

package cn.javayuli.demo.controller;import cn.javayuli.demo.entity.JavaCoder;
import cn.javayuli.demo.service.JavaCoderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.List;/*** @author GuiLin Han* @since 1.0.0*/
@RestController
public class JavaCoderController {@Resourceprivate JavaCoderService javaCoderService;@GetMapping("/java-coder/all")public List<JavaCoder> getList() {return javaCoderService.list();}
}

5、在SpringBoot启动类上使用Mapper扫描注解

package cn.javayuli.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@MapperScan(basePackages = "cn.javayuli.demo.mapper")
@SpringBootApplication
public class MybatisDemoApplication {public static void main(String[] args) {SpringApplication.run(MybatisDemoApplication.class, args);}}

那MybatisPlus是在哪里和SpringBoot进行集成的呢?

二、MybatisPlus集成原理

可以看到,启动类上加入了一个@MapperScan的注解,这个注解是Mybatis的,所以猜测这个注解大概率起到了关键作用,那我们就一起来看看:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {.....
}

这里有个@Import注解,引入的MapperScannerRegistrarImportBeanDefinitionRegistrar的子类,通过Spring中bean注册机制可以知道,@Import引入的ImportBeanDefinitionRegistrar子类,会在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry中通过ImportBeanDefinitionRegistrar的addImportBeanDefinitionRegistrar方法,直接注册BeanDefinitionregistry中,从而可以生成bean。

MapperScannerRegistrar

撸下源码:

 public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {.......@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 获取MapperScan注解AnnotationAttributes mapperScanAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));if (mapperScanAttrs != null) {registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,generateBaseBeanName(importingClassMetadata, 0));}}void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {// 创建一个MapperScannerConfigurer的BeanDefinitionBuilderBeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);......   // 向registry中注册BeanDefinitionregistry.registerBeanDefinition(beanName, builder.getBeanDefinition());}......
}

以上就是为了构造一个MapperScannerConfigurer对象的BeanDefinition,它的属性大部分都是从@MapperScan这个注解中取出来的,它的作用就是收集各种参数,方便后续扫描。

MapperScannerConfigurer

image-20231023154115098

通过MapperScannerConfigurer的继承关系可以得出以下几个信息:

  • 实现了BeanDefinitionRegistryPostProcessor接口,Spring会主动调用其postProcessBeanDefinitionRegistrypostProcessBeanFactory方法。
  • 实现了InitializingBean接口,所以在初始化bean时,Spring会主动调用其afterPropertiesSet方法。

其中就postProcessBeanDefinitionRegistry起到了关键作用,来具体看看:

  @Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {if (this.processPropertyPlaceHolders) {processPropertyPlaceHolders();}ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setAddToConfig(this.addToConfig);scanner.setAnnotationClass(this.annotationClass);scanner.setMarkerInterface(this.markerInterface);scanner.setSqlSessionFactory(this.sqlSessionFactory);scanner.setSqlSessionTemplate(this.sqlSessionTemplate);scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);scanner.setResourceLoader(this.applicationContext);scanner.setBeanNameGenerator(this.nameGenerator);scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);if (StringUtils.hasText(lazyInitialization)) {scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));}if (StringUtils.hasText(defaultScope)) {scanner.setDefaultScope(defaultScope);}scanner.registerFilters();scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));}

这里就是为了使用ClassPathMapperScanner的扫描功能,将Mybatis的mapper扫描出来,让Spring进行管理。

ClassPathMapperScanner

  @Overridepublic Set<BeanDefinitionHolder> doScan(String... basePackages) {// 调用父类的扫描方法Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)+ "' package. Please check your configuration.");} else {// 处理扫描出来的beanDefinitionprocessBeanDefinitions(beanDefinitions);}return beanDefinitions;}	

super.doScan调用的是父类ClassPathBeanDefinitionScanner的scan方法,大致就是通过basePackages路径,扫描本地文件中的.class文件,得到类元数据,再根据excludeFilter、includeFilter进行过滤后,最后组装成BeanDefinition。

在processBeanDefinitions中,最关键的步骤是:

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
definition.setBeanClass(this.mapperFactoryBeanClass);

这个mapperFactoryBeanClass是ClassPathMapperScanner类中定义的一个常量,是一个FactoryBean

private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

也就是说,Context在实例化我们的Mapper的时候,解析到的需要实例化的类其实是MapperFactoryBean,最终初始化后得到的单例bean其实是MapperFactoryBean.getObject()的对象

MapperFactoryBean

既然知道了我们自己写的Mapper的实例是由MapperFactoryBean产生的,那我们来究其原因,它是怎么一个流程。

先来看下类图:

image-20231023154124351

顶层就两个接口,一个是FactoryBean,赋予其生产bean的能力,另一个是InitializingBean,肯定也是想利用afterPropertiesSet方法来做一些操作。那我们来分别对这两个接口进行分析。

1、InitializingBean

public abstract class DaoSupport implements InitializingBean {/** Logger available to subclasses. */protected final Log logger = LogFactory.getLog(getClass());@Overridepublic final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {// Let abstract subclasses check their configuration.checkDaoConfig();// Let concrete implementations initialize themselves.try {initDao();}catch (Exception ex) {throw new BeanInitializationException("Initialization of DAO failed", ex);}}protected abstract void checkDaoConfig() throws IllegalArgumentException;protected void initDao() throws Exception {}}

可以看到,就是定义了两个步骤,一个是检查dao(数据库操作对象,亦称mapper)的配置,另一个是初始化dao,具体操作由子类实现。

那继续往下走,看看SqlSessionDaoSupport

public abstract class SqlSessionDaoSupport extends DaoSupport {private SqlSessionTemplate sqlSessionTemplate;public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);}}@SuppressWarnings("WeakerAccess")protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}public final SqlSessionFactory getSqlSessionFactory() {return (this.sqlSessionTemplate != null ? this.sqlSessionTemplate.getSqlSessionFactory() : null);}public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {this.sqlSessionTemplate = sqlSessionTemplate;}public SqlSession getSqlSession() {return this.sqlSessionTemplate;}public SqlSessionTemplate getSqlSessionTemplate() {return this.sqlSessionTemplate;}@Overrideprotected void checkDaoConfig() {notNull(this.sqlSessionTemplate, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");}}

可以看到,其中定义了SqlSessionTemplate这个变量,就说明SqlSessionDaoSupport的子类可以拿到SqlSessionTemplate进行一些操作,SqlSessionTemplate是对SqlSession操作定义的模板方法,可以执行数据库增删改查。

回到主流程,MapperFactoryBean重写了checkDaoConfig方法:

  @Overrideprotected void checkDaoConfig() {// 调用父类的checkDaoConfigsuper.checkDaoConfig();notNull(this.mapperInterface, "Property 'mapperInterface' is required");Configuration configuration = getSqlSession().getConfiguration();// 如果需要添加到Configuration,且Configuration中还没有当前mapperif (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {try {// 添加当前mapper到Configuration中configuration.addMapper(this.mapperInterface);} catch (Exception e) {logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);throw new IllegalArgumentException(e);} finally {ErrorContext.instance().reset();}}}

在MybatisPlus中,此处configuration是MybatisConfiguration,最终调用的是MybatisMapperRegistry的addMapper方法:

    @Overridepublic <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {return;}boolean loadCompleted = false;try {knownMappers.put(type, new MybatisMapperProxyFactory<>(type));MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}

可以看到,在MybatisMapperRegistry中,会为每一个mapper分配在一个MybatisMapperProxyFactory

InitializingBean这个接口这条路就分析完了,接下来对FactoryBean这边进行分析。

2、FactoryBean

@Override
public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);
}

getSqlSession()返回的是SqlSessionTemplate

@Override
public <T> T getMapper(Class<T> type) {return getConfiguration().getMapper(type, this);
}

getConfiguration()返回的是MybatisConfiguration,

@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mybatisMapperRegistry.getMapper(type, sqlSession);
}

MybatisMapperRegistry中:

@Override
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}

可以看到,mapper实例是从MybatisMapperProxyFactory创建来的。

MybatisMapperProxyFactory

public class MybatisMapperProxyFactory<T> {@Getterprivate final Class<T> mapperInterface;@Getterprivate 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);}
}

此factory中,是创建了一个jdk动态代理的对象,而jdk动态代理的灵魂就是InvocationHandlerMybatisMapperProxy就是InvocationHandler的子类,所以,搞清楚MybatisMapperProxy的动作,就知道了mapper接口能具体执行的重要步骤。

MybatisMapperProxy

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else {return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}

这里也没啥大的逻辑,就是将mapper的具体调用交给这个sqlSession去执行,而这个sqlSession,其实就是上面一直传递下来的SqlSessionTemplate对象。

三、总结

Mybatis通过@MapperScan指定mapper包路径,并据此扫描BeanDefinition,并设置其class为MapperFactoryBean。在实例化时,得到的是factoryBean生成的一个jdk动态代理对象。在使用mapper时,实际是使用SqlSessionTemplate的能力。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PPSSPP (PSP游戏模拟器)最新版安装使用教程

PPSSPP优势 1、目前唯一的也是最好的psp模拟器 可运行绝大多数psp游戏且运行高速&#xff0c;即使是低配手机也能游玩经典大作。 2、支持自定义调节虚拟手柄和实体手柄连接 ppsspp模拟器支持使用虚拟手柄或者连接实体手柄游玩&#xff0c;同时还可以自定义调节按键选项。 …

泄密零容忍!迅软科技打造设计图纸安全防线,助您无忧创作!

对于建筑设计、鞋服设计、动漫设计、平面设计等设计行业而言&#xff0c;海量设计图纸都以电子数据的形式存在企业的终端电脑上&#xff0c;这些图纸蕴含着企业的核心竞争资源&#xff0c;一旦泄露将给企业带来巨大的经济损失。 因此&#xff0c;迅软科技采用了先进的数据加密技…

超声波清洗机器哪个品牌好用?这四款都夸的超声波清洗机

超声波清洗是一种先进的清洗技术&#xff0c;它利用高频振动产生微小气泡来对物体进行清洗。这些微小气泡在物体表面不断振动&#xff0c;使得污垢和油脂被震碎并脱落。这种方法可以有效地去除眼镜上的污垢、油脂和细菌&#xff0c;从而提高眼镜的清洁度。与传统的清洗方法相比…

利用sql语句来统计用户登录数据的实践

目录 1 基本数据情况2 统计每个用户每个月登录次数3 将日期按月显示在列上4 总结 1 基本数据情况 当需要对用户登录情况进行统计时&#xff0c;SQL是一个非常强大的工具。通过SQL&#xff0c;可以轻松地从数据库中提取和汇总数据&#xff0c;并以适合分析和报告的方式进行呈现…

电子签名软件,在教育行业中如何应用?

电子签名软件简化签署流程&#xff0c;降低签署门槛&#xff0c;让更多人便捷地参与到签署中来。 微签作为国内电子签名软件的拓荒者之一&#xff0c;拥有19年的研发应用经验&#xff0c;提供专业的企业电子签名服务。微签的电子签名软件广泛应用于审批场景&#xff0c;实现高…

嵌入式总线技术详解

1. 总线概述 1.1 总线定义 总线&#xff08;Bus&#xff09;是计算机各种功能部件之间传送信息的公共通信干线它是由导线组成的传输线束&#xff0c;按照计算机所传输的信息种类&#xff0c;计算机的总线可以划分为数据总线、地址总线和控制总线&#xff0c;分别用来传输数据…

人工智能与供应链行业融合:预测算法的通用化与实战化

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 让我们一起深入探索人工智能与供应链的融合&#xff0c;以及预测算法在实际应用中的价值&#xff01;&#x1f50d;&#x1f680; 文章目录 前言供应链预测算法的基本流程统计学习模型与机…

冲刺高端,OPPO不太OK?

所有人都知道OPPO有一个高端梦&#xff0c;而折叠屏似乎就是其弯道超车实现高端化的关键所在。然而如今看来&#xff0c;折叠屏手机在市场的表现似乎并没有成为主流&#xff0c;这或许也意味着OPPO距离自己的高端梦似乎还有些距离。 更高端&#xff0c;往往意味着更多的利润和价…

广州华锐视点:基于VR元宇宙技术开展法律法规常识在线教学,打破地域和时间限制

随着科技的飞速发展&#xff0c;人类社会正逐渐迈向一个全新的时代——元宇宙。元宇宙是一个虚拟的、数字化的世界&#xff0c;它将现实世界与数字世界紧密相连&#xff0c;为人们提供了一个全新的交流、学习和娱乐平台。在这个充满无限可能的元宇宙中&#xff0c;法律知识同样…

【小黑嵌入式系统第十课】μC/OS-III概况——实时操作系统的特点、基本概念(内核任务中断)、与硬件的关系实现

文章目录 一、为什么要学习μC/OS-III二、嵌入式操作系统的发展历史三、实时操作系统的特点四、基本概念1. 前后台系统2. 操作系统3. 实时操作系统&#xff08;RTOS&#xff09;4. 内核5. 任务6. 任务优先级7. 任务切换8. 调度9. 非抢占式&#xff08;合作式&#xff09;内核10…

从 0 到 1 开发一个 node 命令行工具

G2 5.0 推出了服务端渲染的能力&#xff0c;为了让开发者更快捷得使用这部分能力&#xff0c;最写了一个 node 命令行工具 g2-ssr-node&#xff1a;用于把 G2 的 spec 转换成 png、jpeg 或者 pdf 等。基本的使用如下&#xff1a; $ g2-ssr-node g2png -i ./bar.json -o ./bar.…

水库大坝安全在线监测系统守护水利工程的坚实屏障

随着科技的发展&#xff0c;水库大坝的安全监测已经进入了一个全新的时代。过去&#xff0c;我们无法实时监测大坝的安全状况&#xff0c;只能在灾难发生后进行补救&#xff0c;现在&#xff0c;通过WX-DB1水库大坝安全在线监测系统&#xff0c;我们能够在第一时间掌握大坝的运…