在别人网站上建设频道或栏目相关法律规定,湖南企业网站营销设计,河南自助建站seo公司,建一个平台网站需要多少钱文章目录简介环境搭建源码解析基础环境#xff1a;JDK17、SpringBoot3.0、mysql5.7
储备知识#xff1a;《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》
简介
基于SpringBoot的Mybatis源码解析#xff1a;
1.如何对mapper实例化bean
在加载BeanDefinition时JDK17、SpringBoot3.0、mysql5.7
储备知识《【Spring6源码・AOP】AOP源码解析》、《JDBC详细全解》
简介
基于SpringBoot的Mybatis源码解析
1.如何对mapper实例化bean
在加载BeanDefinition时会将SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer加载到注册表中以供后续进行实例化。
而且在此期间mapper接口已经实例化完成了后续从缓存中取出即可。
初始化时
第一步使用SqlSessionFactoryBean来生成SqlSessionFactory。生成过程中使用了MapperAnnotationBuilder解析mapper接口上的注解放到Configuration中然后放到SqlSessionFactory里把创建的SqlSessionFactory实例放到bean缓存池中。
第二步使用使用SqlSessionTemplate构造器创建SqlSessionTemplate对象其中用了jdk代理方式创建了SqlSession代理对象。需说明SqlSessionTemplate采用单例模式并通过TransactionSynchronizationManager中的ThreadLocalMapObject, Object保存线程对应的SqlSession即DefaultSqlSession这个不是线程安全的实现session的线程安全。
第三步通过MapperFactoryBean来实例化mapper接口也是通过jdk代理方式创建的mapper代理对象并把依赖的SqlSessionFactory和SqlSessionTemplate注入mapper中。
2.如何执行mapper 执行mapper方法的过程主要是先通过两个代理类即先执行mapper代理实现类MapperProxy的invoke方法然后执行SqlSessionTemplate代理实现类的invoke方法然后进入DefaultSqlSession相应方法中这里会根据mapper的限定名获取MappedStatement然后调用Executor相应方法而Executor是封装了jdbc的操作所以最终是通过jdbc执行sql最后再把执行的结果解析返回。
在spring容器初始化的过程中使用JDK动态代理生成mapper的代理对象然后在执行mapper方法的过程利用代理机制执行目标方法最终底层通过jdbc执行sql。
附 SqlSessionFactoryBean用于生成SqlSessionFactory 的FactoryBean。 Configuration存放所有mybatis配置信息包括mapper接口、mapper.xml、mybatis-config.xml等 XMLConfigBuilder 解析 mybatis-config.xml 配置并存放到Configuration中 XMLMapperBuilder 解析 mapper.xml配置并存放到Configuration中在这里完成了mapper接口与mapper.xml的绑定 MapperAnnotationBuilder解析mapper接口上的注解将sql信息存放到configuration中 SqlSessionFactoryBuilder: 实际用于创建 SqlSessionFactory SqlSessionFactory 用于创建 SqlSession SqlSession Mybatis工作的最顶层API会话接口所有访问数据库的操作都是通过SqlSession来的。 SqlSessionTemplate 内部维护有 SqlSession 的代理对象解耦Mapper和SqlSession的关键对象。 MapperScannerConfigurer用于扫描所有mapper接口并将mapper接口生成beanDefinition放到beanFactory的bean定义注册表中然后再把beanDefinition中的mapper的beanClass转换成MapperFactoryBean这么做是为了第一可以通过遍历bean定义注册表找到mapper的beanDefinition用于实例化bean第二可以通过MapperFactoryBean的getObject方法来实例化bean通过jdk代理生成了bean的代理对象。 环境搭建
依赖
!-- MySQL驱动 --
dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactId
/dependency
!-- 集成MyBatis --
!-- 引入 3.0.0 版本的 mybatis-spring-boot-starter正式版 --
dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion3.0.0/version
/dependencymapper
public interface UserMapper {Select(select * from user where id #{id})User select(String id);
}controller main
SpringBootApplication
MapperScan(basePackages com.ossa.web3.mapper)
public class AppRun {public static void main(String[] args) {SpringApplication.run(AppRun.class, args);}
}application.yml
spring:datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://issavior-aliyun-rds.mysql.rds.aliyuncs.com:3306/test?useUnicodetruecharsetEncodingutf-8useSSLfalseserverTimezoneAsia/ShanghaiallowMultiQueriestrueusername: rootpassword: root访问http://localhost:8080/user/test 响应结果{id:c5329f3b-3e98-4722-8faf-e87d9b981871,name:Marry,age:18}
源码解析
因为项目引入了mybatis-spring-boot-starter依赖此依赖又依赖了mybatis-spring-boot-autoconfigure根据SpringBoot可以自动装配的机制会扫面所有包下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件然后加载其中的类封装成BeanDefinition当然加载之前会通过spring-autoconfigure-metadata.properties配置文件进行条件判断。判断是否要加载其中的类。 看看此META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
总共导入两个类MybatisLanguageDriverAutoConfiguration、MybatisAutoConfiguration。 先看imports文件中的MybatisAutoConfiguration类这个类会在封装BeanDefinition的时候加载 分析一下配置类的注解
org.springframework.context.annotation.Configuration 配置类ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) ConditionalOnClass注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效底层是通过Class.forName()判断是否存在该类。ConditionalOnSingleCandidate(DataSource.class) ConditionalOnSingleCandidate表示当指定Bean只有一个或者虽然有多个但是指定首选Bean这时候才会将其放到容器中。EnableConfigurationProperties(MybatisProperties.class) 将properties和yml配置文件属性转化为bean对象使用。AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class }) AutoConfigureAfter 在加载配置的类之后再加载当前类
看一下Mybatis的配置属性
EnableConfigurationProperties(MybatisProperties.class)原理其扫描配置文件根据prefix匹配对应属性然后填充。
都有注释大家可以自己看 通过之前对Spring和SpringBoot的源码分析我们可知自动装配操作在组件加载之后所以我们先来看看启动类上的注解 这个MapperScan注解用来扫描相关mapper接口并生成对应的代理对象。
看一下类的相关介绍 该注解上有一个Import(MapperScannerRegistrar.class)注解意味着在启动类加载的同时会将此注解后的类MapperScannerRegistrar加载进IOC容器。
步入MapperScannerRegistrar
该类实现了ImportBeanDefinitionRegistrar接口重写了registerBeanDefinitions方法。 既然该类实现了ImportBeanDefinitionRegistrar接口重写了registerBeanDefinitions方法。
那么就会在如下这里进行加载 最后会执行该类实现后的方法 总结一句话MapperScan通过Import引入MapperScannerRegistrar类MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类并执行它的registerBeanDefinitions方法即执行MapperScannerRegistrar#registerBeanDefinitions方法。
这个registerBeanDefinitions方法首先通过上面72行代码获取获取MapperScan注解属性信息 步入registerBeanDefinitions方法
首先使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象。
之后将注解属性中的值赋给builder对象。 最后注册该BeanDefinition此时MapperScannerConfigurer对象已经注入IOC容器了这里划重点一会要用到。 顺便看一下MapperScannerConfigurer这个类
这个类实现了BeanDefinitionRegistryPostProcessor接口在bean的生命周期中会调用其postProcessBeanDefinitionRegistry方法 在invokeBeanFactoryPostProcessors方法里会执行两个接口按先后执行顺序为 第一个是BeanDefinitionRegistryPostProcessor 第二个是BeanFactoryPostProcessor。 步入postProcessBeanDefinitionRegistry方法
首先构建ClassPathMapperScanner对象然后填充属性。
再通过下面两行代码调用scan方法。
// 注册Filter因为上面构造函数我们没有使用默认的Filter
// 有两种FilterincludeFilters要扫描的excludeFilters要排除的
scanner.registerFilters();
// 扫描basePackagebasePackage可通过,; \t\n来填写多个
// ClassPathMapperScanner重写了doScan方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ,; \t\n));步入scan方法 步入doScan方法 进入父类
这个我们之前讲过扫包解析存入注册表中返回。 返回 步入processBeanDefinitions方法 在这个processBeanDefinitions方法中把每个mapper的Bean定义的BeanClass设置为mapperFactoryBeanClass这样做是为了让后面创建bean时可以使用MapperFactoryBean来创建bean。这里为什么要把mapper的BeanClass设置为mapperFactoryBeanClass因为mapper是接口接口不能实例化所以mybatis中就把mapper的beanDefinition的beanClass定义为mapperFactoryBeanClass利用mapperFactoryBeanClass是通过getObject()来进行实例化即通过jdk代理的方式生成的代理对象。 到这里mapper就扫描完事了 处理用MapperScan注解扫描还可以在mapper类上加上Mapper注解扫描。
会通过MapperScannerRegistrarNotFoundConfiguration这个配置类导入AutoConfiguredMapperScannerRegistrar类AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口当执行refresh的invokeBeanFactoryPostProcessors会加载所有实现了ImportBeanDefinitionRegistrar接口的类并执行它的registerBeanDefinitions方法即执行AutoConfiguredMapperScannerRegistrar.registerBeanDefinitions。进而生成MapperScannerConfigurer的bean定义放到IOC容器beanFactory中后面的逻辑和MapperScan是一样的。
当然如果用了MapperScan这个注解是不会加载MapperScannerRegistrarNotFoundConfiguration这个配置类的因为这个类上有一个注解ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })如果当前容器中存在MapperScannerConfigurer这个类这个配置类就不会生效上面我们已经加载过了不信往上翻翻我还做了标记。
提一嘴这个ConditionalOnMissingBean注解这个注解底层有一个搜索策略SearchStrategy 最终会返回true和false。 都添加进注册表后会对bean进行实例化和初始化初始化又会进行属性填充按着正常的业务逻辑
controller依赖的serviceservice依赖mappermapper依赖sqlSessionTemplatesqlSessionTemplate依赖SqlSessionFactory所以最终会先实例化SqlSessionFactory。
在MybatisAutoConfiguration这个配置类中Bean声明sqlSessionFactory方法:
首先获取配置文件和mapper对应的文件最后返回SqlSessionFactory如果没有则回去创建。
buildSqlSessionFactory通过XMLConfigBuilder解析mybatis配置通过XMLMapperBuilder解析mapper.xml的配置然后生成mappedStatements、resultMaps、sqlFragments以及其他的配置最终放到Configuration里供后面使用。 紧接着实例化SqlSessionTemplate 最后会调用SqlSessionTemplate构造方法用JDK动态代理创建对象。 最后到实例化maper了。
回到initializeBean方法
在前面bean已经被替换成MapperFactoryBean。 MapperFactoryBean实现了InitializingBean接口所以先执行afterPropertiesSet最终执行MapperFactoryBean.checkDaoConfig。 步入checkDaoConfig方法
首先检查配置mapper接口
78行代码如果configuration中没有该mapper接口则加载因为有关sql有两种写法一种是我们这个demo这种注解形式一种就是xml文件写sql这种方式如果是xml这种xmlMapperBuilder.parse()就会加载mapper接口这里就不会进入。如果使用注解这种方式就会进入这里。
在里面解析了mapper接口上的注解然后填充configuration。 步入addMapper方法首先判断是否为接口再构建MapperAnnotationBuilder解析器再去解析。 步入parse方法 步入parseStatement方法
这里根据userMapper接口解析接口上的注解。 填充完configuration之后基本就加载差不多了。
我们来看一下mapper接口的执行流程
发送请求http://localhost:8080/user/test
进入断点 步入selectById方法进入了mapper代理类的invoke方法中 最终执行execute方法根据增删改查四种操作继续接下来的逻辑
我们这里是SELECT获取参数调用selectOne方法 步入selectOne方法 因为sqlSessionTemplate是SqlSessionInterceptor代理创建的所以接下来走SqlSessionInterceptor.invoke方法。 创建sqlSession执行代理的真正的方法如果被事务管理则提交事务最后关闭sqlSession。 步入selectOne方法 步入selectList方法
最终调用首先获取Statement再去调用执行器的查询方法 最终会调用query方法预编译执行sql。再调用handleResultSets方法处理结果并返回。