Mybatisi和Spring整合源码分析
一、MybatisSpring的使用
1.创建 Maven 工程。
2.添加依赖,代码如下
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7-ybe</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6-ybe</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.16</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.20.RELEASE</version>
</dependency>
3.添加实体如下,
package com.ybe.entity;
import java.io.Serializable;
public class Book implements Serializable {
int id;
double price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
4.添加 Mapper接口以及BookMapper.xml文件,
public interface BookMapper {
Book getBook(@Param("id") int id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ybe.mapper.BookMapper">
<cache></cache>
<select id="getBook" resultType="com.ybe.entity.Book">
select * from book where id = #{id}
</select>
</mapper>
5.添加 BookService 和 BookServiceImpl代码如下,
package com.ybe.service;
import com.ybe.entity.Book;
public interface BookSerivce {
Book getBook(int id);
}
package com.ybe.service.impl;
import com.ybe.entity.Book;
import com.ybe.mapper.BookMapper;
import com.ybe.service.BookSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class BookServiceImpl implements BookSerivce {
@Autowired
BookMapper bookMapper;
public Book getBook(int id) {
return bookMapper.getBook(id);
}
}
6.添加配置类,代码如下
package com.ybe.config;
@Configuration
@MapperScan(basePackages = {"com.ybe.mapper"})
@ComponentScan(basePackages = {"com.ybe"})
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:com/ybe/mapper/*.xml"));
factoryBean.setTypeAliases(Book.class);
return factoryBean;
}
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/aopTest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8");
return dataSource;
}
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
7.添加主类代码,代码如下
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(MyBatisConfig.class);
BookSerivce bookSerivce = configApplicationContext.getBean(BookSerivce.class);
Book book = bookSerivce.getBook(1);
System.out.println(book.getId());
二、Mybatis和Spring的整合
因为Mybatis中使用的是Mapper.class 接口来找到数据库sql语句,并且是通过SqlSessionFactory的SqlSession来连接数据库和执行Sql语句的。所以Mybatis和Spring的整合,其实就是把Mybatis的SqlSessionFactory类和Mapper.Class接口注入到SpringIOC中。并且SqlSessionFactory类中的事务管理对象(SpringManagedTransactionFactory )会集成Spring的事务。
整个整合的过程分为两部分,第一部分 Mapper接口注入;第二部分 SqlSessionFactoryBean 注入。
2.1 Mapper 接口注入
试想一下我们在写Mapper接口的时候并没有写实现类,只是写了Mapper.xml文件。那在注入到Spring容器中,具体的实现类是啥?我这里直接给答案,Mapper接口在Spring容器中对应的实现类是一个MapperFactoryBean的类,最终存在beanFactory.singletonObjects中。MapperFactoryBean实现了FactoryBean接口,在获取Mapper接口的时候,默认返回的是MapperFactoryBean的getObject()方法,其中具体返回xxxMapper接口的动态代理类(JDK代理方式)。动态代理类会进行缓存。
原理
通过配置@MapperScan(basePackages = {"com.ybe.mapper"}) 注解,向 BeanDefinitionRegistry 中添加类型为 MapperScannerConfigurer 的BeanDefinition对象并且初始化对象相关属性,在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 中会进行调用MapperScannerConfigurer 的postProcessBeanDefinitionRegistry 方法,该方法会扫描配置的包路径下的Mapper接口class文件,生成BeanClass为MapperFactoryBean的ScannedGenericBeanDefinition,注册到 BeanDefinitionRegistry 中。
源码解析
- @MapperScan注解源码如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
- 在 org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 方法中,会先执行 org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions的方法,其中会执行MapperScannerRegistrar类的registerBeanDefinitions方法,向 BeanDefinitionRegistry 注册了一个类型为 MapperScannerConfigurer的BeanDefinition对象。代码如下,
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。MapperScannerConfigurer主要用来扫描具体的 Mapper接口class文件,生成BeanClass类型为MapperFactoryBean的ScannedGenericBeanDefinition对象后注入 BeanDefinitionRegistry 中。具体类图如下
在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors中会接着执行,MapperScannerConfigurer的postProcessBeanDefinitionRegistry 方法。postProcessBeanDefinitionRegistry中大概逻辑为
搜索指定包下面的Mapper接口,
// 开始搜索 basePackage
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
生成ScannedGenericBeanDefinition,并以Mapper接口名称为BeanName注入到BeanDefinitionRegistry对象中。
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
然后再设置ScannedGenericBeanDefinition的BeanClass类型为MapperFactoryBean类。 关键代码代码如下
// 设置 definition 的 构造函数的参数值类型为 beanClassName,
// 在创建 MapperFactoryBean 时,会根据beanClassName创建类,然后把类作为参数调用MapperFactoryBean带参数的构造方法
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 设置 definition 的 Bean 类型为 MapperFactoryBean.class
definition.setBeanClass(this.mapperFactoryBeanClass);
每个Mapper接口文件对应的BeanDefinition为 ScannedGenericBeanDefinition,BeanDefinition的BeanClass实现类为 MapperFactoryBean 类。此时查看beanFactory的 beanDefinitionMap 中的值,如下图
MapperFactoryBean是一个泛型类,泛型用来表示不同接口类型。继承了SqlSessionDaoSupport类,该类中存储了Maybatis的SqlSession工厂类。MapperFactoryBean也是一个实现了FactoryBean的类,用来返回具体的类型以及根据类型来生成具体的Mapper接口代理类。
@Override
public T getObject() throws Exception {
// 返回根据接口类型返回 SqlSession中的Mapper代理对象
return getSqlSession().getMapper(this.mapperInterface);
}
/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}
在beanFactory.preInstantiateSingletons()方法中会把BeanDefinition生成具体的Bean对象,在创建 MapperFactoryBean 对象的时候会调用带参数的构造方法(上面有具体说明)。因为在配置类中我们注入了SqlSessionFactoryBean对象(具体解析过程在下面章节讲解),SqlSessionFactoryBean对象实现了FactoryBean接口,在Srping容器中会返回SqlSessionFactory类型对象。在给MapperFactoryBean属性赋值的时候会把SqlSessionFactoryBean的实际SqlSessionFactory对象赋值给sqlSessionTemplate属性。最终会在beanFactory.singletonObjects对象中添加以Mapper接口名称为key,以 MapperFactoryBean 类型为value的 记录。
在获取Mapper接口的Bean对象的时候,会调用getObject()方法,其中会调SqlSessionTemplate的getMapper(),代码如下
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
- 以上调用Configuration的getMapper()方法,configuration就是Mybatis的类了。代码如下,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口类型获取已经注册的对象
return mapperRegistry.getMapper(type, sqlSession);
}
- 返回创建的动态代理对象,这里返回的动态代理对象之后不会更新beanFactory.singletonObjects的对象,并且会进行缓存处理。关键代码如下
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据 Mapper 接口类型获取已经注册的对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 创建动态代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
- 总结一下,beanFactory.singletonObjects关于Mapper接口存储的Bean对象是泛型的MapperFactoryBean对象。但是根据Mapper接口获取Bean对象会返回上面的动态代理对象,并且动态代理对象会缓存在beanFactory.factoryBeanObjectCache中,这是因为MapperFactoryBean实现的FactoryBean接口。
2.1 SqlSessionFactoryBean 注入
SqlSessionFactoryBean介绍
通过@Bean方式可以将SqlSessionFactoryBean对象注入到Spring容器,SqlSessionFactoryBean对象在MapperFactoryBean对象中会用到。注入代码如下,
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException {
// 创建 SqlSessionFactoryBean 的类
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 设置数据源
factoryBean.setDataSource(dataSource);
// 设置配置文件路径
factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml"));
// 设置 mybaits.xml 文件
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:com/ybe/mapper/*.xml"));
// 设置别名
factoryBean.setTypeAliases(Book.class);
return factoryBean;
}
SqlSessionFactoryBean的类继承关系图如下,
SqlSessionFactoryBean实现了InitializingBean接口 ,会在初始化后会执行afterPropertiesSet方法,其中会调用buildSqlSessionFactory()方法进行SqlSessionFactory的创建。SqlSessionFactoryBean也实现了FactoryBean接口,在容器中如果获取SqlSessionFactoryBean对象会返回SqlSessionFactory对象,并且也会在beanFactory.factoryBeanObjectCache进行缓存。getObject源码如下,
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
buildSqlSessionFactory()具体逻辑
1. 根据 configLocation 创建 XMLConfigBuilder 以及 Configuration对象
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
targetConfiguration = xmlConfigBuilder.getConfiguration();
2. 读取SqlSessionFactoryBean的属性对象给 targetConfiguration 赋值
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
}
......
3. 解析主配置文件
xmlConfigBuilder.parse();
4. targetConfiguration设置环境变量,如果配置的transactionFactory 事务工厂类为 null,则创建 SpringManagedTransactionFactory 事务工厂类,该事务工厂会直接调用org.springframework.jdbc.datasource.DataSourceUtils去获取数据库连接对象,所以和Spring的事务进行了集成。代码如下,
// 设置环境变量,如果事务工程类为 null,则创建 SpringManagedTransactionFactory 事务工厂类
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
5. 如果 mapperLocations 不为null ,则循环遍历 xxxMapper.xml 文件流,解析之后给 targetConfiguration 的相关对象赋值。代码如下,
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 构建 XMLMapperBuilder 对象,进行mapper.xml 文件资源解析
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
- 总结一下,整个过程比较简单,其主要就是Mybaits初始化的过程,更详细Mybatis初始化过程请参考上一篇文章。
Mybatisi和Spring整合源码分析的更多相关文章
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- Spring AOP 源码分析系列文章导读
1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...
- 精尽Spring MVC源码分析 - 寻找遗失的 web.xml
该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- Spring Security 源码分析(四):Spring Social实现微信社交登录
社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...
- spring事务源码分析结合mybatis源码(一)
最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...
- spring AOP源码分析(三)
在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...
随机推荐
- Spring Boot-自动配置之底层原理
一.SpringBoot启动的时候加载主配置类,开启了自动配置的功能 @SpringBootApplication public class SpringBoot02Application { pub ...
- 招商银行 KubeVela 离线部署实践
招商银行云平台开发团队自 2021 年开始接触 KubeVela,并探索 KubeVela 在招商银行云平台的落地实践,借此提升云原生应用交付与管理能力.同时因为金融保险行业的特殊性,网络安全管控措施 ...
- LC-209
给定一个含有 n 个正整数的数组和一个正整数 target . 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, nums ...
- LibreOffice(开源免费办公软件)
LibreOffice(开源免费办公软件) 官方地址 中文网站:https://zh-cn.libreoffice.org/ 下载地址: https://zh-cn.libreoffice.org/d ...
- Hyperledger Fabric无系统通道启动及通道的创建和删除
前言 在Hyperledger Fabric组织的动态添加和删除中,我们已经完成了在运行着的网络中动态添加和删除组织,但目前为止,我们启动 orderer 节点的方式都是通过系统通道的方式,这样自带系 ...
- netty系列之:netty中的核心MessageToMessage编码器
目录 简介 框架简介 MessageToMessageEncoder MessageToMessageDecoder MessageToMessageCodec 总结 简介 在netty中我们需要传递 ...
- Photoshop图片处理在线网页使用无需下载绿色
今天给大家推荐一个ps在线版网页 实测使用效果不错,绿色简介,无需下载,不卡顿一般的电脑配置都可以带起来 因为是在线的所以是精简版的,但是一般ps软件有的工具,功能他都有,比较适合及时性使用 废话不多 ...
- 有意思的CVE-2022-0337复现
前言 前两天在刷tw,看到了个比较有意思的一个CVE漏洞,价值奖励是10000美刀,比较好奇的是价值10000美刀的漏洞是什么样子的,漏洞利用就是需要在浏览器中进行用户交互才能触发该漏洞,但由于 Wi ...
- Java 15 新特性:文本块
大家好,我是DD,今天继续来学点Java的新特性! 假设有这样一个场景,我们需要做一个工具.用来自动生成项目文档,文档可以通过浏览器查看,所以最后产出物肯定是一堆html文件.为了让这些html文件更 ...
- kill -9 进程杀不掉,怎么办?
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 用ps和grep命令寻找僵尸进程 ps -A -ostat,ppid,pid,cmd | gr ...