一、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 中。

源码解析
  1. @MapperScan注解源码如下
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
  1. 在 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());
  1. MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。MapperScannerConfigurer主要用来扫描具体的 Mapper接口class文件,生成BeanClass类型为MapperFactoryBean的ScannedGenericBeanDefinition对象后注入 BeanDefinitionRegistry 中。具体类图如下

  2. 在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);
  1. 每个Mapper接口文件对应的BeanDefinition为 ScannedGenericBeanDefinition,BeanDefinition的BeanClass实现类为 MapperFactoryBean 类。此时查看beanFactory的 beanDefinitionMap 中的值,如下图

  2. 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;
}
  1. 在beanFactory.preInstantiateSingletons()方法中会把BeanDefinition生成具体的Bean对象,在创建 MapperFactoryBean 对象的时候会调用带参数的构造方法(上面有具体说明)。因为在配置类中我们注入了SqlSessionFactoryBean对象(具体解析过程在下面章节讲解),SqlSessionFactoryBean对象实现了FactoryBean接口,在Srping容器中会返回SqlSessionFactory类型对象。在给MapperFactoryBean属性赋值的时候会把SqlSessionFactoryBean的实际SqlSessionFactory对象赋值给sqlSessionTemplate属性。最终会在beanFactory.singletonObjects对象中添加以Mapper接口名称为key,以 MapperFactoryBean 类型为value的 记录。

  2. 在获取Mapper接口的Bean对象的时候,会调用getObject()方法,其中会调SqlSessionTemplate的getMapper(),代码如下

@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
  1. 以上调用Configuration的getMapper()方法,configuration就是Mybatis的类了。代码如下,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口类型获取已经注册的对象
return mapperRegistry.getMapper(type, sqlSession);
}
  1. 返回创建的动态代理对象,这里返回的动态代理对象之后不会更新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);
}
}
  1. 总结一下,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 + "'");
}
  1. 总结一下,整个过程比较简单,其主要就是Mybaits初始化的过程,更详细Mybatis初始化过程请参考上一篇文章。

Mybatisi和Spring整合源码分析的更多相关文章

  1. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  2. Spring AOP 源码分析系列文章导读

    1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...

  3. 精尽Spring MVC源码分析 - 寻找遗失的 web.xml

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  4. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. Spring Security 源码分析(四):Spring Social实现微信社交登录

    社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯微博.淘宝.豆瓣.MSN.Google等社会化媒体账号登录该网站. 前言 ...

  8. spring事务源码分析结合mybatis源码(一)

    最近想提升,苦逼程序猿,想了想还是拿最熟悉,之前也一直想看但没看的spring源码来看吧,正好最近在弄事务这部分的东西,就看了下,同时写下随笔记录下,以备后查. spring tx源码分析 这里只分析 ...

  9. spring AOP源码分析(三)

    在上一篇文章 spring AOP源码分析(二)中,我们已经知道如何生成一个代理对象了,那么当代理对象调用代理方法时,增强行为也就是拦截器是如何发挥作用的呢?接下来我们将介绍JDK动态代理和cglib ...

随机推荐

  1. RFC 标准文档

    RFC 标准文档 什么是 RFC ? RFC(Request For Comments)意即"请求评论",包含了关于Internet的几乎所有重要的文字资料.如果你想成为网络方面的 ...

  2. 如何基于 ZEGO SDK 实现 Windows 一对一音视频聊天应用

    互联网发展至今,实时视频和语音通话越来越被大众所依赖. 今天,我们将会继续介绍如何基于ZEGO SDK实现音视频通话功能,前两篇文章分别介绍了Android,Flutter平台的实现方式,感兴趣的小伙 ...

  3. Java进阶 JVM 内存与垃圾回收篇(一)

    JVM 1. 引言 1.1 什么是JVM? 定义 Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境) 好处 一次编译 ,到处运行 自动内存管理,垃 ...

  4. Java学习day6

    今天跟着教学视频做了个简易的学生管理系统 在编写完全部代码之后出现了在空白处右键没有run as选项的问题,通过csdn与博客园上的多个帖子介绍,得知是jdk配置不对,正确配置后问题得到解决 明天学习 ...

  5. 【LeetCode】24.两两交换链表中的节点

    24.两两交换链表中的节点 知识点:链表 题目描述 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点.你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换). 示例 示例 1 ...

  6. React + TypeScript + Taro前端开发小结

    前言 项目到一段落,先来记录一下,本文以前端新手的角度记录React.TypeScript.Taro相关技术的开发体验以及遇到的问题和解决方法. 之前总说要学React(这篇博客:代码使我头疼之Rea ...

  7. k8s入门之Ingress(七)

    Ingress 的功能其实很容易理解:所谓 Ingress,就是 Service 的"Service",代理不同后端 Service 而设置的负载均衡服务. 一.安装ingress ...

  8. 【第六课】SHELL和计划任务(学习笔记)

    4月15日学习笔记

  9. 【Git】一台电脑与多个分布式版本管理平台连接

    六. 一台电脑与多个版本控制平台 1. 一台电脑同时通过ssh连接github和码云gitee 打开git bash 进入.ssh cd ~/.ssh 分别生成两个平台的公钥和私钥 $ ssh-key ...

  10. 2003031121-浦娟-python数据分析第四周作业-第二次作业

    项目 内容 课程班级博客链接 20级数据班(本) 作业链接 Python第四周作业第二次作业 博客名称 2003031121-浦娟-python数据分析第四周作业-matolotlib的应用 要求 每 ...