一、Mybatis回顾

  1、MyBatis介绍

  Mybatis是一个半ORM框架,它使用简单的 XML 或注解用于配置和原始映射,将接口和Java的POJOs(普通的Java 对象)映射成数据库中的记录。

  2、Mybatis整体架构

二、Spring Boot整合Mybatis + Druid

  1、在应用中导入maven依赖如下:

     <!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--database pool-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.8</version>
</dependency>
<!--mysql数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

  2、在应用中加配置

  ① 配置Druid数据源参数:

#配置数据源
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false
spring.datasource.druid.username=root
spring.datasource.druid.password=123qwe
spring.datasource.druid.driverClassName=com.mysql.jdbc.Driver
spring.datasource.druid.initialSize: 5
spring.datasource.druid.minIdle: 5
spring.datasource.druid.maxActive: 20
spring.datasource.druid.maxWait: 60000
spring.datasource.druid.timeBetweenEvictionRunsMillis: 60000
spring.datasource.druid.minEvictableIdleTimeMillis: 300000
spring.datasource.druid.validationQuery: SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle: true
spring.datasource.druid.testOnBorrow: false
spring.datasource.druid.testOnReturn: false
spring.datasource.druid.poolPreparedStatements: true
spring.datasource.druid.filters: stat,wall
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize: 20
spring.datasource.druid.useGlobalDataSourceStat: true
spring.datasource.druid.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

  ② 编写Druid数据源属性接收类:

/**
* @desc: 自定义druid的属性
* @author: toby
*/
@ConfigurationProperties(prefix = "spring.datasource.druid")
@Data
public class DruidDataSourceProperties {
private String username;
private String password;
private String url;
private String driverClassName;
private Integer initialSize;
private Integer maxActive;
private Integer minIdle;
private Long maxWait;
......
}

  ③ 编写Druid数据源配置类:

/**
* @desc: 自定义druid配置,如不自定义,配置文件设置的属性不生效自行测试
* @author: toby
*/
@Configuration
@EnableConfigurationProperties(value = DruidDataSourceProperties.class)
@MapperScan(basePackages="com.toby.mapper", value="sqlSessionFactory")
public class DruidDataSourceConfig { @Autowired
private DruidDataSourceProperties druidDataSourceProperties; @Bean
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUsername(druidDataSourceProperties.getUsername());
druidDataSource.setPassword(druidDataSourceProperties.getPassword());
druidDataSource.setUrl(druidDataSourceProperties.getUrl());
druidDataSource.setDriverClassName(druidDataSourceProperties.getDriverClassName());
druidDataSource.setInitialSize(druidDataSourceProperties.getInitialSize());
druidDataSource.setMinIdle(druidDataSourceProperties.getMinIdle());
druidDataSource.setMaxActive(druidDataSourceProperties.getMaxActive());
druidDataSource.setMaxWait(druidDataSourceProperties.getMaxWait());
druidDataSource.setFilters(druidDataSourceProperties.getFilters());
druidDataSourceProperties.setPoolPreparedStatements(druidDataSourceProperties.getPoolPreparedStatements());
return druidDataSource;
}
}

  3、在应用中加@MapperScan注解,他的主要作用就是扫描basePackage包下面的TobyMapper接口,然后getBean的时候通过JDK的动态代理,生成代理对象,所以我们程序中看到的是TobyMapper接口,其实是被动态代理过的。验证方式很简单,DEUBG到TobyMapper的方法里面,就可以发现其就是个动态代理

/**
* @desc: spring boot 启动类
* @author: toby
*/
@SpringBootApplication
@MapperScan(basePackages="com.toby.mapper")
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}
}

三、源码解析

  1、Mybatis自动装配

  自动装配的流程图:

  具体详细见Spring Boot系列(二):Spring Boot自动装配原理解析中的Spring Boot自动装配流程图。Mybatis的自动配置类给我们配置了什么组件,我们接下来看下MybatisAutoConfiguration类

  ① SqlSessionFactory:当容器中没有SqlSessionFactory这个类型的Bean的时候,Spring就加载该组件。

  ② SqlSessionTemplate:同样当容器中没有SqlSessionTemplate这个类型的Bean的时候,Spring就加载该组件。

  到此SqlSessionFactory和SqlSessionTemplate组件有了。

  2、@MapperScan注解

  @MapperScan注解:他的作用就是扫描basePackages包下面的TobyMapper接口,然后getBean("tobyMapper")的时候把该接口通过JDK的动态代理,生成代理对象,用于和数据库打交道。

  ① 从@MapperScan入手:

  ② 导入了MapperScanner注册类MapperScannerRegistrar:

  它是一个ImportBeanDefinitionRegistrar,Spring在通过@Import导入Bean的时候,会调用其registerBeanDefinitions,往Spring容器中注册bean定义信息,以便后面可以通过getBean获取到被注册进行的bean的定义信息所对应的Bean,其往Spring容器中注册的是MapperFactoryBean类型的Bean定义信息,为什么是MapperFactoryBean类型,而不是TobyMapper类型?原因很简单,Spring容器在getBean的时候,会忽略掉接口,接口是不能new的,而Spring容器默认在实例化的时候就是通过调用beanDefinition的beanClass属性所对应的类的无参构造方法。

  ③ 进去到注册bean定义信息的registerBeanDefinitions方法如下:

  ④ 进入到ClassPathMapperScanner的doScan,其作用是扫描basePackages所有的包,这里ClassPathMapper Scanner继承了Spring的包扫描ClassPathBeanDefinitionScanner

  重写(覆盖)了判断是否是候选的Component方法isCandidateComponent,因为Spring默认的isCandidateComponent是会过滤掉接口的,显然不满足,所以重写了该方法

  /**
* Spring默认的,独立的非接口,非抽象类
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
} /**
* ClassPathMapperScanner的可以是独立的接口
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

  接下来进入到处理TobyMapper的bean的定义信息方法:

  ⑤ 处理TobyMapper的bean定义信息,主要由3个重要改动:

//第一:修改构造函数为有参的构造函数;
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
//第二:修改bean的class为MapperFactoryBean,该MapperFactoryBean是FactoryBean;
definition.setBeanClass(this.mapperFactoryBean.getClass());
//第三:修改注入类型为按照类型注入;
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

  到此,扫描TobyMapper的时候,往Spring容器中注册的是beanClass为MapperFactoryBean,一个有参数的构造函数,按照类型注入的这么一个Bean定义信息。

  3、通过getBean("tobyMapper")获取TobyMapper的动态代理类

  我们知道,此时的tobyMapper的bean定义信息中的beanClass的属性是MapperFactoryBean.class,而MapperFactoryBean又是一个FactoryBean,FactoryBean的特点就是在getBean的时候会调用其getObject方法;

  ① 我们找到MapperFactoryBean的getObject方法:

  ② 我们再进入org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper方法,其实已经到了Mybatis的逻辑了。

  ③ 最后调用到org.apache.ibatis.binding.MapperRegistry#getMapper,其实就是创建JDK的动态代理了。

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}

  代理的逻辑在org.apache.ibatis.binding.MapperProxy#invoke方法,到此tobyMapper创建完成,可以操作数据库。

四、扫描Mapper和Mapper动态代理对象生成流程图

Spring Boot系列(三):Spring Boot整合Mybatis源码解析的更多相关文章

  1. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  2. Mybatis源码解析3——核心类SqlSessionFactory,看完我悟了

    这是昨晚的武汉,晚上九点钟拍的,疫情又一次来袭,曾经熙熙攘攘的夜市也变得冷冷清清,但比前几周要好很多了.希望大家都能保护好自己,保护好身边的人,生活不可能像你想象的那么好,但也不会像你想象的那么糟. ...

  3. Mybatis源码解析(一) —— mybatis与Spring是如何整合的?

    Mybatis源码解析(一) -- mybatis与Spring是如何整合的?   从大学开始接触mybatis到现在差不多快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,比如: 它是如 ...

  4. springboot整合mybatis源码分析

    springboot整合mybatis源码分析 本文主要讲述mybatis在springboot中是如何被加载执行的,由于涉及的内容会比较多,所以这次只会对调用关系及关键代码点进行讲解,为了避免文章太 ...

  5. Mybatis源码解析(三) —— Mapper代理类的生成

    Mybatis源码解析(三) -- Mapper代理类的生成   在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...

  6. mybatis源码-解析配置文件(三)之配置文件Configuration解析

    目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...

  7. Mybatis源码解析,一步一步从浅入深(三):实例化xml配置解析器(XMLConfigBuilder)

    在上一篇文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码 ,中我们看到 代码:XMLConfigBuilder parser = new XMLConfigBuilder(read ...

  8. Mybatis源码解析(四) —— SqlSession是如何实现数据库操作的?

    Mybatis源码解析(四) -- SqlSession是如何实现数据库操作的?   如果拿一次数据库请求操作做比喻,那么前面3篇文章就是在做请求准备,真正执行操作的是本篇文章要讲述的内容.正如标题一 ...

  9. Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取

    在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...

随机推荐

  1. 一步步教你用Prometheus搭建实时监控系统系列(一)——上帝之火,普罗米修斯的崛起

    上帝之火 本系列讲述的是开源实时监控告警解决方案Prometheus,这个单词很牛逼.每次我都能联想到带来上帝之火的希腊之神,普罗米修斯.而这个开源的logo也是火,个人挺喜欢这个logo的设计. 本 ...

  2. Spring main方法中怎么调用Dao层和Service层的方法

    在web环境中,一般serviceImpl中的dao之类的数据库连接都由容器启动的时候创建好了,不会报错.但是在main中,没有这个环境,所以需要获取环境: ApplicationContext ct ...

  3. 【高并发】高并发环境下构建缓存服务需要注意哪些问题?我和阿里P9聊了很久!

    写在前面 周末,跟阿里的一个朋友(去年晋升为P9了)聊了很久,聊的内容几乎全是技术,当然了,两个技术男聊得最多的话题当然就是技术了.从基础到架构,从算法到AI,无所不谈.中间又穿插着不少天马行空的想象 ...

  4. python爬虫基础要学什么,有哪些适合新手的书籍与教程?

    一,爬虫基础: 首先我们应该了解爬虫是个什么东西,而不是直接去学习带有代码的内容,新手小白应该花一个小时去了解爬虫是什么,再去学习带有代码的知识,这样所带来的收获是一定比你直接去学习代码内容要多很多很 ...

  5. pandas_一维数组与常用操作

    # 一维数组与常用操作 import pandas as pd # 设置输出结果列对齐 pd.set_option('display.unicode.ambiguous_as_wide',True) ...

  6. go项目dockerfile最佳实践

    1. 前言 2. 不需要cgo情况下的最佳实践 3. 依赖cgo情况下的最佳实践 1. 前言 这几天在构建golang编写的web项目中,关于dockerfile编写的一些总结 可能是单纯我比较菜(大 ...

  7. 一款功能简约到可怜的SQL 客户端

    你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...

  8. Python 字典(Dictionary) str()方法

    Python 字典(Dictionary) str()方法 描述 Python 字典(Dictionary) str() 函数将值转化为适于人阅读的形式,以可打印的字符串表示.高佣联盟 www.cge ...

  9. PHP log1p() 函数

    实例 返回不同数的 log(1+number): <?phpecho(log1p(2.7183) . "<br>");echo(log1p(2) . " ...

  10. 搭建Redis主从复制的集群

    在主从复制模式的集群里,主节点一般是一个,从节点一般是两个或多个,写入主节点的数据会被复制到从节点上,这样一旦主节点出现故障,应用系统能切换到从节点去读写数据,这样能提升系统的可用性.而且如果再采用主 ...