【源码】-MyBatis-如何系统地看源码

写在前面

  前段时间做过一个项目,期间用到了动态数据源dynamic-datasource,经历了dbcp2的数据库连接池没有生效到排查定位、MyBatis多种数据库产品兼容、手写MyBatis拦截器等事情。

  花费了好久,一直在打磨这篇文章(不知道花费这么长时间写文章有没有意义,但互联网总得留下点儿什么吧~)。最终,千呼万唤始出来。本文就从源码的角度来系统地看看原理是什么,能学到些什么。如有说的不正确的地方,欢迎指正。


目录

  • 写在前面
  • 一、环境说明
  • 二、为什么?如何做
  • 三、动态数据源部分
    • (一)动态数据源加载、连接池创建
  • 四、MyBatis部分
    • (一) MyBatis核心组件加载
    • (二)MapperProxy初始化
    • (三)连接池的使用、数据库厂商加载、拦截器生效
  • 五、收获
  • 六、附录(plantuml脚本)
    • activity_baomidou_dynamicdatasource.puml
    • activity_mybatis_sqlsessionfactorybean.puml
    • activity_mybatis_mapperproxy.puml
    • activity_mybatis_conn_interceptor.puml
    • class_baomidou.puml
    • class_mybatis.puml
  • 写在后面
  • 系列文章


一、环境说明

名称 说明
mybatis版本 mybatis-3.4.6.jar
mybatis-spring版本 mybatis-spring-1.3.1.jar
mybatis-spring-boot版本 mybatis-spring-boot-autoconfigure-1.3.0.jar
dynamic-datasource-spring-boot-starter版本 dynamic-datasource-spring-boot-starter-3.5.2.jar
commons-dbcp2版本 commons-dbcp2-2.8.0.jar
IDEA编辑器 2019
PlantUML插件 IDEA / VsCode插件

二、为什么?如何做

说正题前,我们先思考一下,为什么要看源码?

我想可能有几种场景:

1、出问题了,不得不看。
比如在项目中引入了什么包、配置或者做了什么改动,导致项目无法启动或者报错了
2、求知欲。有疑问,带着问题看。
比如本文将要讨论的:dbcp2连接池是什么时机创建的?如何创建的?又是怎么使用的?MyBatis拦截器插件是如何生效的?等等
3、面试。 不过比起八股文,自己研究一下体会会更深。

那看源码,我们能收获什么?
个人认为看源码可以去切实地体会优秀的代码设计,了解高手是怎么做的,包括设计模式的运用、扩展点、设计原则等等。思考如果自己以后遇到类似问题该如何运用。不熟悉设计模式这部分内容的朋友,可以去参考这篇文章【GitHub】- design-pattern(设计模式)

另外看源码,笔者还有一个提示:不要一下子要求全搞懂,否则你会越陷越深… 看到最后,不知所云。根据当下的水平,逐步丰富自己的体系,建议带着问题,带着疑问,点到为止(问题搞懂,不要无限蔓延)

那究竟该如何看?这里我谈一谈自己的见解(正文内容也是这么做的)。

稍微扩展一下,延展到如何学习一个新的框架或者新的知识点
1、通读官方文档、GitHub的README
知道这个框架是做什么的,主要用于解决什么问题,了解框架的架构图
2、结合官方Demo,熟悉基本操作API
3、自己动手
写Demo,本地调试。梳理流程,主要包括2部分内容:类图和活动图
画类图,能让你快速全面地了解到这个类是干什么用的,有什么样的体系(继承、实现、依赖以及关联关系)
画活动图,能让你知道这些类的调用关系、这些类是如何相互作用使用的。

不熟悉类图和活动图的这部分朋友,可以去参考这篇文章【UML】- 类图

看源码,首先看什么能最快地找到突破口?

我认为,
首先看它的pom.xml(看依赖关系,pom中有什么jar包可能就会具备哪些功能)
其次,看源码的包命名(优秀的代码内聚做的比较好)
然后,从resource开始着手(可能有一些配置需要提前了解)

好了,接下里,我们回归正题。
以下从2个部分开始说,主要包括动态数据源和MyBatis。


三、动态数据源部分

【源码】-MyBatis-如何系统地看源码_第1张图片

(一)动态数据源加载、连接池创建

首先找到 dynamic-datasource-spring-boot-starter-3.5.2.jar
我们在resource下找到了META-INF/spring.factories,咦,这是什么?这不就是自动装配么。好了,找到切入口了。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceAutoConfiguration

看看 DynamicDataSourceAutoConfiguration 干了什么事情

# DynamicDataSourceAutoConfiguration
1.将 yml 中的配置信息(数据源url/driver、连接池等等配置信息)构建成Bean,加入到Spring容器
2.通过 @Import 导入creator的自动配置类 DynamicDataSourceCreatorAutoConfiguration,进行creator初始化
	2.1 创建一个 DefaultDataSourceCreator(List dataSourceCreators),
		构造注入dataSourceCreators,与DataSourceCreator绑定关系(也就是dbcp2的creator)
	2.2 判断classpath中有dbcp2的包,创建 Dbcp2DataSourceCreator
3.创建 YmlDynamicDataSourceProvider()
	3.1 通过构造方法和 dataSourcePropertiesMap 进行了绑定
	3.1 通过继承,属性注入 DefaultDataSourceCreator,和creator进行了绑定
4.创建 DynamicRoutingDataSource 路由
	4.1 @Autowired 属性注入 provider,也就是 YmlDynamicDataSourceProvider
	4.2 实现了InitializingBean,初始化Bean时,会调用@afterPropertiesSet

我们先梳理一下这里边的几个绑定关系:
DynamicRoutingDataSource 路由包含一个 YmlDynamicDataSourceProvider,
YmlDynamicDataSourceProvider 中有 dataSourcePropertiesMap 和 DefaultDataSourceCreator,
DefaultDataSourceCreator 中有 List,包含了dbcp的creator。

该初始化的进行初始话,该绑定的关系的绑定关系。
接下来我们重点看 DynamicRoutingDataSource#afterPropertiesSet()
动态数据源加载的过程,也就是连接池的创建过程

# DynamicRoutingDataSource#afterPropertiesSet()
1.调用 provider#loadDataSources()

# YmlDynamicDataSourceProvider
2.调用 defaultDataSourceCreator#createDataSource

# DefaultDataSourceCreator
3.调用 dbcp2#doCreateDataSource(dataSourceProperty)

# BasicDataSource
4.调用 dbcp2的连接池去创建数据源 dataSource#start()
	4.1 for循环配置文件中的 initialSize,connectionPool.addObject() 添加到LinkedBlockingDeque中

至此,连接池就创建完毕了。

以下是活动图和类图,
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分
【源码】-MyBatis-如何系统地看源码_第2张图片
【源码】-MyBatis-如何系统地看源码_第3张图片

有了类图就很明显了,这里面有3个体系:

DataSource(spring实现了AbstractDataSource)
DataSourceCreator
DynamicDataSourceProvider

相应地对应了有3个抽象类:

1.AbstractRoutingDataSource
这个类在连接池创建的时候没用到,这里也说一下,在获取时,为重载DataSource#getConnection 添加一个determineDataSource()获取连接池的操作,
具体实现在DynamicRoutingDataSource中

2.AbstractDataSourceCreator
抽象连接池创建器,这里边抽象出一个 doCreateDataSource(DataSourceProperty dataSourceProperty)由具体的实现类dbcp2、druid、Hikari实现
另外,在doCreateDataSource前后做了一些通用逻辑
// 源码
dataSourceInitEvent.beforeCreate(dataSourceProperty)
DataSource dataSource = doCreateDataSource(dataSourceProperty);
dataSourceInitEvent.afterCreate(dataSource)

3.AbstractDataSourceProvider
多数据源加载接口,默认的实现为从yml信息中加载所有数据源。
抽象出接口,就可以扩展一些其他加载数据源的方式

四、MyBatis部分

(一) MyBatis核心组件加载

【源码】-MyBatis-如何系统地看源码_第4张图片

首先找到 mybatis-spring-boot-autoconfigure-1.3.0.jar
在resource下找到了META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

看一下MybatisAutoConfiguration做了什么事情。

# MybatisAutoConfiguration
1.@AutoConfigureAfter,优先注入datasource数据源
2.通过datasource数据源,构造方式 SqlSessionFactoryBean,绑定了数据源
	2.1 设置datasource、configuration
	2.2 由于实现了InitializingBean,初始化Bean时,会调用@afterPropertiesSet
		2.2.1 处理configuration、添加Interceptor、databaseId、
			创建事务工厂、解析xml对象,最后构建一个DefaultSqlSessionFactory
3.通过sqlsesssionfactory创建sqlSessionTemplate
	3.1 构造方法中隐藏了一个sqlSessionProxy代理,
	它代理了SqlSession接口,通过SqlSessionInterceptor的invoke实现逻辑

说明:这里SqlSessionTemplate定义了一套操作Mybatis的模板(实际上方法的执行调用的都是SqlSession的实现类方法)

因为框架想在操作完方法之后处理一些事务的提交、session的关闭等操作(实现方式是使用代理Proxy,代理SqlSession)

以下是活动图:
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分
【源码】-MyBatis-如何系统地看源码_第5张图片

(二)MapperProxy初始化

【源码】-MyBatis-如何系统地看源码_第6张图片

我们知道接口是无法执行方法的,这里MyBatis把所有的mapperInterface代理成了MapperProxy。以下是MapperProxy的创建过程活动图:
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分
【源码】-MyBatis-如何系统地看源码_第7张图片

(三)连接池的使用、数据库厂商加载、拦截器生效

【源码】-MyBatis-如何系统地看源码_第8张图片

以下是MyBatis操作整体流程活动图,
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分
【源码】-MyBatis-如何系统地看源码_第9张图片

我们以一个示例 baseMapper.queryByList(map) 作为入口来说明,
执行baseMapper#queryByList()实际上调用的是 mapperproxy#invoke(前面提到了mapperproxy代理了baseMapper)

mapperproxy调用了mapperMethod#execute(sqlSession, args)

# MapperMethod
1.根据 SqlCommandType 执行SQL,也就是判断 CRUD,然后通过SqlSession做相应操作

这里的重点来了,SqlSession现在是什么?SqlSession是SqlSessionTemplate,也就是会调用SqlSessionTemplate#selectList
在源码里你会看到接着调用的是 sqlSessionProxy#selectList,

sqlSessionProxy我们前面提到过是代理的SqlSession,InvokeHandler是SqlSessionInterceptor
那么,此时就会调用SqlSessionInterceptor#invoke

# SqlSessionInterceptor
2.这是一个模板的方法,进行了3个操作
	2.1 获取真正要处理的 getSqlSession
	2.2 执行方法
	2.3 判断事务操作,提交事务
	2.4 关闭session

接下来详细说一下[2.1] getSqlSession()干了什么事情,它通过前面创建的 DefaultSqlSessionFactory 创建一系列的组件。
# DefaultSqlSessionFactory
2.1.1根据datasource创建tx事务,transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit)
2.1.2.创建Executor执行器,configuration.newExecutor(tx, execType)
	2.1.2.1 这个executor执行器会经过拦截器链(分页、自定义的属性填充)的包装,
		这里又是一个Plugin的代理,会代理executor里面的方法,在真正方法执行前,先执行这些拦截器的操作
	2.1.2.2 最终返回一个DefaultSqlSession对象

当执行[2.2] 的方法时,通过反射的方式调用就是DefaultSqlSession#selectList
#DefaultSqlSession
2.2.1 这里边会去获取到databaseId的sql
2.2.2 先去执行拦截器链中的拦截器(分页、属性填充)
2.2.3 要获取Connection
	2.2.3.1 从事务中获取datasource,此时的datasource在DynamicRoutingDataSource的datasourceMap中
	2.2.3.2 拿到datasource后(dbcp2的datasource),会调用getConnection,从 LinkedBlockingDeque#pollXxx()

以下是MyBatis的类图,
看看类图,你至少知道什么是datasource、什么是connection、什么是sqlsession,对他们的关系有了进一步的理解了吧。
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分

【源码】-MyBatis-如何系统地看源码_第10张图片


五、收获

  • 面向接口编程,抽象类实现通用逻辑,也可以通过模板的方式实现通用逻辑
  • 一些自动配置,我们可以通过starter自动装配实现,关系的构建可以通过Bean初始化,去实现InitializingBean,进而通过属性或者构造方法的方式去注入一些你需要的Bean
  • 可以通过注入Event的这种方式去做一个before和after的事情,也可以通过代理的方式实现
  • 优秀名的命名、编码风格

六、附录(plantuml脚本)

activity_baomidou_dynamicdatasource.puml

@startuml
skinparam style strictuml
skinparam sequenceMessageAlign direction
skinparam roundcorner 20
skinparam sequenceParticipant underline
autoactivate on

title 【动态数据源加载及连接池创建】活动图

'baomidou
participant ddsa_config << (C,#ADD1B2) DynamicDataSourceAutoConfiguration >>
note over of ddsa_config #aqua
    自动装配,核心Bean创建的入口
end note

participant dds_properties << (C,#ADD1B2) DynamicDataSourceProperties >>
participant ds_property << (C,#ADD1B2) DataSourceProperty >>
note over dds_properties, ds_property
    yml属性对应的Bean(内外层)
end note

participant dds_creator_a_config << (C,#ADD1B2) DynamicDataSourceCreatorAutoConfiguration >>
note over of dds_creator_a_config
    creator自动配置
end note

participant ads_creator << (A,#A8DEDF) AbstractDataSourceCreator >>
participant dds_creator << (C,#ADD1B2) DefaultDataSourceCreator >>
participant dbcp2ds_creator << (C,#ADD1B2) Dbcp2DataSourceCreator >>
note over dds_creator_a_config, dbcp2ds_creator
    creator体系
end note

participant ads_provider << (A,#A8DEDF) AbstractDataSourceProvider >>
participant ydds_provider << (C,#ADD1B2) ymlDynamicDataSourceProvider >>
note over of ydds_provider #aqua
    组件的关系绑定
end note
note over ads_provider, ydds_provider
    provider体系
end note

participant drds << (C,#ADD1B2) DynamicRoutingDataSource >>
note over of drds #aqua
    动态数据源加载入口
end note

'dbcp2
participant basic_ds << (C,#ADD1B2) BasicDataSource >>
note over of basic_ds
    dbcp2连接池
end note

-> ddsa_config: dynamic-datasource-spring-boot-starter-3.5.2.jar 自动装配 META-INF/spring.factories\n EnableAutoConfiguration=c.b.d.d.s.b.autoconfigure.DynamicDataSourceAutoConfiguration
==yml配置文件的加载以及初始化==

group yml配置构建成Bean,加入spring容器
    ddsa_config -> dds_properties: @EnableConfigurationProperties
    dds_properties -> ds_property: private Map datasource \n = new LinkedHashMap<>()
    ds_property --> ddsa_config: yml填充后加入spring容器
end

group @Import导入creator
    ddsa_config -> dds_creator_a_config: @Import
        group creator初始化
            group 创建默认的creator
                dds_creator_a_config -> dds_creator_a_config: dataSourceCreator(List dataSourceCreators)\n 与DataSourceCreator(dbcpCreator)绑定关系
                dds_creator_a_config -> dds_creator: new DefaultDataSourceCreator()
                dds_creator --> dds_creator_a_config: 提供了createDataSource(dataSourceProperty)方法
            end

            group 创建dbcp2的creator
                dds_creator_a_config -> dds_creator_a_config: dbcp2DataSourceCreator()
                dds_creator_a_config -> dbcp2ds_creator: new Dbcp2DataSourceCreator()
                dbcp2ds_creator --> dds_creator_a_config: 提供了 doCreateDataSource(DataSourceProperty dataSourceProperty) 方法
            end

            group 创建其他的creator(@ConditionalOnClass(BeeDataSource.class))
                dds_creator_a_config -> dds_creator_a_config: ...
            end
        end
     dds_creator_a_config --> ddsa_config: DynamicDataSourceCreatorAutoConfiguration 被创建
end

group 构造方法 注入 DynamicDataSourceProperties 和 List
    ddsa_config -> ddsa_config: DynamicDataSourceAutoConfiguration(properties, dataSourcePropertiesCustomizers)\n 目的是拿到yml中的配置为其他Bean传参
end

group 创建YmlDynamicDataSourceProvider
    ddsa_config -> ydds_provider : ymlDynamicDataSourceProvider(),\n new对象把datasource这个Map(每一个数据源)作为构造参数传入, 相当于和dataSourcePropertiesMap进行了绑定
    ydds_provider -> ads_provider: @Autowigreen DefaultDataSourceCreator defaultDataSourceCreator\n属性注入相当于和creator进行了绑定
    ads_provider --> ydds_provider

    ydds_provider --> ddsa_config: 提供loadDataSources()方法
end

group 创建 DynamicRoutingDataSource 路由
    ddsa_config -> drds: dataSource(),\n new对象,依据DynamicDataSourceProperties的属性设置自身,比如primary属性
        group @Autowired属性注入providers
            drds -> drds: @Autowired List providers: 此处的provider就是创建YmlDynamicDataSourceProvider
        end
    drds --> ddsa_config: 由于实现了InitializingBean,提供 afterPropertiesSet() 方法

    ==连接池创建==
    group #EEE 实现了InitializingBean,重写afterPropertiesSet
        drds -> drds #aqua: @Override afterPropertiesSet
        drds -> ydds_provider: provider.loadDataSources()

        ydds_provider -> ads_provider: createDataSourceMap(dataSourcePropertiesMap)

        ads_provider -> dds_creator: createDataSource(DataSourceProperty dataSourceProperty)
        dds_creator -> ads_creator: createDataSource(DataSourceProperty dataSourceProperty)
        ads_creator -> ads_creator:  __abstract doCreateDataSource(dataSourceProperty)__\n 由连接池实现,以dbcp2为例

        ads_creator -> dbcp2ds_creator: doCreateDataSource(DataSourceProperty dataSourceProperty)
        dbcp2ds_creator -> basic_ds: start()

        group #EEE 真正创建连接池
            basic_ds -> basic_ds: createDataSource()
            basic_ds -> basic_ds: for循环连接池initialSize\n connectionPool.addObject()\r 添加到LinkedBlockingDeque中
        end

        basic_ds --> drds
    end
end

@enduml

activity_mybatis_sqlsessionfactorybean.puml

@startuml
skinparam style strictuml
skinparam sequenceMessageAlign direction
skinparam roundcorner 20
skinparam sequenceParticipant underline
autoactivate on

title 【MyBatis核心组件加载】活动图
'ibatis
participant mba_config << (C,#ADD1B2) MybatisAutoConfiguration >>
note over of mba_config #aqua
    自动装配,核心Bean创建的入口
end note

'spring
participant dsa_config << (C,#ADD1B2) DataSourceAutoConfiguration >>

participant ssf_bean << (C,#ADD1B2) SqlSessionFactoryBean >>
note over of ssf_bean #aqua
    构建SqlSessionFactory的入口
end note

'mybatis
participant ss_template << (C,#ADD1B2) SqlSessionTemplate >>

participant ssf_builder << (C,#ADD1B2) SqlSessionFactoryBuilder >>
participant dss_factory << (C,#ADD1B2) DefaultSqlSessionFactory >>
note over ssf_bean, dss_factory
    SqlSession相关
end note

-> mba_config: mybatis-spring-boot-autoconfigure-1.3.0.jar 自动装配 META-INF/spring.factories\n EnableAutoConfiguration=o.m.s.b.autoconfigure.MybatisAutoConfiguration
==加载yml配置文件及初始化==

group 优先注入 DataSourceAutoConfiguration
    mba_config -> dsa_config: @AutoConfigureAfter
    dsa_config --> mba_config
end

group 构造方法方式 给私有属性赋值
    mba_config -> mba_config: MybatisProperties\n ObjectProvider\n ResourceLoader\n ObjectProvider\n ObjectProvider mba_config: sqlSessionFactory(DataSource dataSource), new对象,创建 SqlSessionFactoryBean\n//设置setDataSource、setConfiguration、\n//setPlugins、setDatabaseIdProvider、setMapperLocations(解析classpath:/mapper/*Mapper.xml)
    mba_config -> ssf_bean: getObject()
        group #EEE 创建SqlSessionFactory
            ssf_bean -> ssf_bean #aqua: @afterPropertiesSet()
            ssf_bean -> ssf_bean: buildSqlSessionFactory()\n//处理configuration\n//addInterceptor(plugin)\n//setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource))\n//创建SpringManagedTransactionFactory\n//xmlMapperBuilder.parse();\nbuilderAssistant.addMappedStatement(databaseId)\ncreateSqlSource():SqlSource

            ssf_bean -> ssf_builder: this.sqlSessionFactoryBuilder.build(configuration)
            ssf_builder -> ssf_builder: build(config)

            ssf_builder -> dss_factory: new DefaultSqlSessionFactory(config)
            dss_factory --> ssf_bean
        end
    ssf_bean --> mba_config
end

group 创建 SqlSessionTemplate
    mba_config -> mba_config: sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
    mba_config -> ss_template: new SqlSessionTemplate()
        group #EEE 构造方法
            ss_template -> ss_template: SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, \nExecutorType executorType,\nPersistenceExceptionTranslator exceptionTranslator)\n//隐藏了一个代理\nthis.sqlSessionProxy = (SqlSession) newProxyInstance(\nSqlSessionFactory.class.getClassLoader(),\nnew Class[] { SqlSession.class },\nnew SqlSessionInterceptor());
        end
    ss_template --> mba_config
end

@enduml

activity_mybatis_mapperproxy.puml

@startuml
skinparam style strictuml
skinparam sequenceMessageAlign direction
skinparam roundcorner 20
skinparam sequenceParticipant underline
autoactivate on

title 【MyBatis MapperProxy 初始化】活动图
'spring
participant ab_factory << (C,#ADD1B2) AbstractBeanFactory >>

participant mf_bean << (C,#ADD1B2) MapperFactoryBean >>
participant ss_template << (C,#ADD1B2) SqlSessionTemplate >>

participant config << (C,#ADD1B2) Configuration >>
participant mregistry << (C,#ADD1B2) MapperRegistry >>

participant mp_factory << (C,#ADD1B2) MapperProxyFactory >>
participant mproxy << (C,#ADD1B2) MapperProxy >>

-> ab_factory: 项目启动,service 注入 dao
== MapperProxy 的初始化==
ab_factory -> ab_factory: getBean(String name, Class requiredType)\n入参示例:("configPropertiesDao", com.zhht.dao.ConfigPropertiesDao)
ab_factory -> ab_factory: getObjectFromFactoryBean(factory, beanName, !synthetic)

ab_factory -> ab_factory: getObject()

ab_factory -> mf_bean
mf_bean -> mf_bean: getObject()\ngetSqlSession().getMapper(this.mapperInterface)
mf_bean -> ss_template: getMapper(this.mapperInterface)

ss_template -> ss_template: getMapper(Class type)\n//getConfiguration().getMapper(type, this)
ss_template -> config: getMapper(Class type, SqlSession sqlSession)

config -> mregistry: getMapper(Class type, SqlSession sqlSession)
mregistry -> mp_factory:(MapperProxyFactory) knownMappers.get(type)

mp_factory -> mproxy: newInstance(sqlSession):\nnew MapperProxy(sqlSession, mapperInterface, methodCache)
mproxy --> ab_factory

@enduml

activity_mybatis_conn_interceptor.puml

@startuml
skinparam style strictuml
skinparam sequenceMessageAlign direction
skinparam roundcorner 20
skinparam sequenceParticipant underline
autoactivate on

title 【连接池使用及数据库厂商加载】活动图
participant mproxy << (C,#ADD1B2) MapperProxy >>
participant mmethod << (C,#ADD1B2) MapperMethod >>

participant ss_template << (C,#ADD1B2) SqlSessionTemplate >>
note over of ss_template #aqua
    模板方法,控制流程
end note

participant dss_factory << (C,#ADD1B2) DefaultSqlSessionFactory >>
note over of dss_factory #aqua
    核心组件的创建
end note

participant sm_transaction << (C,#ADD1B2) SpringManagedTransaction >>
participant config << (C,#ADD1B2) Configuration >>

participant mstatement << (C,#ADD1B2) MappedStatement >>

participant ds_session << (C,#ADD1B2) DefaultSqlSession >>
participant bexecutor << (C,#ADD1B2) BaseExecutor >>
participant shandler << (C,#ADD1B2) StatementHandler >>

participant interceptor_chain << (C,#ADD1B2) InterceptorChain >>
participant interceptor << (C,#ADD1B2) Interceptor >>
participant plugin << (C,#ADD1B2) Plugin >>
note over interceptor_chain, plugin
    拦截器
end note

participant dr_datasource << (C,#ADD1B2) DynamicRoutingDataSource >>
participant dbc2_datasource << (C,#ADD1B2) BasicDataSource >>
note over dr_datasource, dbc2_datasource
    数据源
end note


-> mproxy: baseMapper.queryByList(map)
==入口==

mproxy -> mproxy: Object invoke(Object proxy, \nMethod method, Object[] args)
mproxy -> mmethod: mapperMethod.execute()

group #EEE 根据 SqlCommandType 执行SQL
    mmethod -> mmethod: Object execute(SqlSession sqlSession, Object[] args)\n // 难点:sqlSession.selectList(),\n 此处的sqlSession是SqlSessionTemplate\n // 调用 this.sqlSessionProxy. selectList(statement, parameter)\n   此处的 sqlSessionProxy的 invokeHandler 是SqlSessionInterceptor
    mmethod -> ss_template: invoke(因为是代理对象,所以要执行 SqlSessionInterceptor.invoke)
end

ss_template -> ss_template: invoke(Object proxy, Method method, Object[] args)
ss_template -> dss_factory: getSqlSession()

==对象的创建==
group  创建 SqlSession
    dss_factory -> dss_factory: openSession(executorType)

    group #EEE 创建组件 tx、Executor、DefaultSqlSession
        dss_factory -> dss_factory: openSessionFromDataSource(ExecutorType execType,\nTransactionIsolationLevel level,\nboolean autoCommit)

        dss_factory -> sm_transaction: 创建tx\ntransactionFactory.newTransaction
        sm_transaction --> dss_factory

        dss_factory -> config: 创建Executor\nconfiguration.newExecutor(tx, execType)
            group #EEE 创建 Executor
                config -> config: 根据 executorTyp\n初始化Batch/Reuse/Simple/CachingExecutor
                config -> interceptor_chain: (Executor) pluginAll(executor)\n包括PageInterceptor、MetaObjectInterceptor等
                interceptor_chain -> interceptor: interceptor.plugin(target)
                interceptor -> interceptor: plugin(Object executor)
                interceptor -> plugin: Plugin.wrap(target, this): 代理对象,\n执行的handler是各类interceptor
                plugin --> config
            end
        config --> dss_factory

        dss_factory -> ds_session: 创建SqlSession\nnew DefaultSqlSession(configuration, executor, autoCommit)
    end

end

ds_session --> ss_template


==方法的执行==
ss_template -> ds_session: method.invoke(sqlSession, args)

    group 具体method的执行
        ds_session -> ds_session: selectList(String statement, Object parameter)
        ds_session -> config: configuration.getMappedStatement(statement)
        config --> ds_session

        ds_session -> mstatement: sqlSource.getBoundSql()\n获取到databaseId的sql
        mstatement --> ds_session

        ds_session -> bexecutor: executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)

        group 拦截器执行
            bexecutor -> plugin: invoke()
            plugin -> interceptor: intercept()
            interceptor --> plugin
            plugin --> bexecutor
        end

        bexecutor -> bexecutor: query()
        bexecutor -> bexecutor: queryFromDatabase()

        bexecutor -> bexecutor: prepareStatement()

        group #EEE 从连接池中获取连接
            bexecutor -> bexecutor: getConnection()

            bexecutor -> sm_transaction: transaction.getConnection()
            sm_transaction -> sm_transaction: openConnection

            sm_transaction -> dr_datasource: dataSource.getConnection()
            dr_datasource -> dr_datasource: getConnection()
            dr_datasource -> dbc2_datasource: dataSourceMap.get(primary)

            dbc2_datasource --> sm_transaction
            sm_transaction --> bexecutor
        end

        bexecutor -> shandler: handler.query(stmt, resultHandler)
        shandler --> bexecutor

        bexecutor --> ds_session
    end

ds_session --> mmethod


==事务的提交==
ss_template -> ds_session: sqlSession.commit(true)
ds_session --> ss_template


==session的关闭==
ss_template -> ds_session: closeSqlSession()
ds_session --> ss_template

@enduml

class_baomidou.puml

@startuml
skinparam linetype ortho

title 【动态数据源】类图

' java
namespace javax.sql #EEE {
    interface Wrapper {
        unwrap(java.lang.Class iface):  T
        isWrapperFor(java.lang.Class iface): boolean
    }

    interface CommonDataSource {
        + getLogWriter(): PrintWriter
        + setLogWriter(PrintWriter out): void
        + setLoginTimeout(int seconds): void
        + getLoginTimeout(): int
        + getParentLogger(): Logger
    }

    interface DataSource {
        --
        + getConnection(): Connection
        + getConnection(String username, String password): Connection
    }

    Wrapper <|-- DataSource
    CommonDataSource <|-- DataSource
}


' spring
namespace org.springframework.beans.factory #EEE {
    interface InitializingBean {
        + {static} afterPropertiesSet(): void
    }
}

namespace org.springframework.jdbc.dataSource #EEE {

    abstract class AbstractDataSource {
    }
    note right: 实现了CommonDataSource的基本操作
    javax.sql.DataSource <|.. AbstractDataSource
}

'baomidou
namespace com.baomidou.dynamic.datasource.ds #EEE {
    abstract class AbstractRoutingDataSource  {
        # {abstract} determineDataSource(): DataSource
        # {abstract} getPrimary(): String
        + getConnection(): Connection
    }

    org.springframework.jdbc.dataSource.AbstractDataSource <|--- AbstractRoutingDataSource
}

namespace com.baomidou.dynamic.datasource #EEE {
    abstract class DynamicRoutingDataSource   {
        --
        + @Override determineDataSource(): DataSource
        + @Override getPrimary(): String
    }

    com.baomidou.dynamic.datasource.ds.AbstractRoutingDataSource <|-- DynamicRoutingDataSource
    org.springframework.beans.factory.InitializingBean <|.. DynamicRoutingDataSource
}

namespace com.baomidou.dynamic.datasource.provider #EEE {
    interface DynamicDataSourceProvider {
        + {static} loadDataSources(): Map
    }

    abstract class AbstractDataSourceProvider {
        - defaultDataSourceCreator: DefaultDataSourceCreator
        - dynamicDataSourceProperties: DynamicDataSourceProperties
        --
        # createDataSourceMap(Map dataSourcePropertiesMap): Map
    }

    class YmlDynamicDataSourceProvider  {
        - dataSourcePropertiesMap: Map
        --
        + @Override loadDataSources(): Map
    }

    DynamicDataSourceProvider <|.. AbstractDataSourceProvider
    AbstractDataSourceProvider <|-- YmlDynamicDataSourceProvider
}

namespace com.baomidou.dynamic.datasource.creator #EEE {
    interface DataSourceCreator {
        + {static} createDataSource(DataSourceProperty dataSourceProperty): DataSource
        + {static} support(DataSourceProperty dataSourceProperty): boolean
    }

    abstract class AbstractDataSourceCreator {
        # properties: DynamicDataSourceProperties
        # dataSourceInitEvent: DataSourceInitEvent
        + {abstract} doCreateDataSource(DataSourceProperty dataSourceProperty): DataSource
        --
        + createDataSource(DataSourceProperty dataSourceProperty): DataSource
    }

    class DefaultDataSourceCreator  {
        - creators: List
        --
        + createDataSource(DataSourceProperty dataSourceProperty): DataSource
    }

    class BasicDataSourceCreator  {
        - creators: List
        --
        static {}
        + @Override doCreateDataSource(DataSourceProperty dataSourceProperty): DataSource
        + @Override support(DataSourceProperty dataSourceProperty): boolean
    }
    note left of BasicDataSourceCreator::static
        builderClass = Class.forName("org.springframework.boot.jdbc.DataSourceBuilder");
        builderClass = Class.forName("org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder");
    end note

    DataSourceCreator <|.. AbstractDataSourceCreator
    AbstractDataSourceCreator <|-- BasicDataSourceCreator
}

namespace com.baomidou.dynamic.datasource.spring.boot.autoconfigure #EEE {
    class DataSourceProperty {
        - type: Class
        - driverClassName: String
        - url: String
        - username: String
        - password: String
        - druid: DruidConfig
        - dbcp2: Dbcp2Config
        ...
    }

    note left of DataSourceProperty::druid
        @NestedConfigurationProperty
    end note

    class DynamicDataSourceProperties {
        + {static} PREFIX: String
        - primary: String
        - strict: Boolean
        - datasource: Map
        - druid: DruidConfig
        - dbcp2: Dbcp2Config
        ...
    }
    note left of DynamicDataSourceProperties::PREFIX
        value = springframework.datasource.dynamic
    end note


    class DynamicDataSourceCreatorAutoConfiguration {
        --
        + dbcp2DataSourceCreator(): Dbcp2DataSourceCreator
        + basicDataSourceCreator(): BasicDataSourceCreator
        + dataSourceCreator(List dataSourceCreators): DefaultDataSourceCreator
    }

    class DynamicDataSourceAutoConfiguration  {
        - properties: DynamicDataSourceProperties
        - dataSourcePropertiesCustomizers: List
        --
        + ymlDynamicDataSourceProvider(): DynamicDataSourceProvider
        + dataSource(): DataSource
        + @Override afterPropertiesSet(): void
    }

    com.baomidou.dynamic.datasource.DynamicRoutingDataSource --[hidden] DynamicDataSourceProperties
    DynamicDataSourceProperties - DataSourceProperty
    DynamicDataSourceProperties -- DynamicDataSourceAutoConfiguration
    DynamicDataSourceAutoConfiguration - DynamicDataSourceCreatorAutoConfiguration: @Import
}

@enduml

class_mybatis.puml

@startuml
skinparam linetype ortho

title 【MyBatis】类图

' java
namespace javax.lang #EEE {
    interface AutoCloseable {
        --
        + close(): void
    }
}

namespace javax.io #EEE {
    interface Closeable {
        --
        + close(): void
    }

    javax.lang.AutoCloseable <|-- Closeable
}

namespace javax.lang.reflect #EEE {

    interface InvocationHandler {
        --
        + invoke(Object proxy, Method method, Object[] args): Object
    }
}

namespace javax.sql #EEE {
    interface Connection {
        --
        + prepareStatement(String sql): PreparedStatement

        + setSavepoint(): Savepoint
        + setAutoCommit(boolean autoCommit): void

        + rollback(Savepoint savepoint): void
        + getTransactionIsolation(): int
        + setTransactionIsolation(): void
        + rollback(): void
        + commit(): void

        + @Override close(): void
    }

    javax.lang.AutoCloseable <|-- Connection
}

'spring
namespace org.springframework.context #EEE {

    interface ApplicationListener {
        --
        + onApplicationEvent(E event): void
    }
}

namespace org.springframework.core.io #EEE {

    interface Resource {
        --
        + exists(): boolean
        + isReadable(): boolean
        + isOpen(): boolean
        + getURL(): URL
        + getFile(): File
    }
}

namespace org.springframework.beans.factory #EEE {

    interface BeanFactory {
        --
        + getBean(Class requiredType): T
        + isSingleton(String name): boolean
        + isPrototype(String name): boolean
    }

    interface FactoryBean {
        --
        + getObject(): T
    }

    interface InitializingBean {
        + {static} afterPropertiesSet(): void
    }
}

'ibatis
namespace org.apache.ibatis #EEE {

    interface SqlSession {
        --
        + getMapper(Class type): T

        + getConfiguration(): Configuration
        + getConnection(): Connection

        + selectOne(String statement): T
        + selectList(String statement): List
        + select(String statement, ResultHandler handler): void

        + insert(String statement): int
        + update(String statement): int
        + delete(String statement): int

        + commit(): void
        + rollback(): void

        + @Override close(): void
    }

    interface SqlSessionFactory {
        --
        + openSession(): SqlSession
        + getConfiguration(): Configuration
    }

    class Configuration {
        # environment: Environment
        # objectFactory: DefaultObjectFactory
        # mappedStatements: Map
    }

    class SqlSessionTemplate {
        - sqlSessionFactory: SqlSessionFactory
        - executorType: ExecutorType
        - sqlSessionProxy: SqlSession, 代理的是 SqlSessionInterceptor
    }

    javax.io.Closeable <|-- SqlSession
    javax.sql.Connection --[hidden] SqlSession

    SqlSession <.. SqlSessionFactory: openSession()
    SqlSessionFactory ..> Configuration: getConfiguration()

    SqlSessionTemplate .|> SqlSession

}

namespace org.apache.ibatis.binding #EEE {

    class MapperProxyFactory {
        - mapperInterface: Class
        - methodCache: Map
        --
        + newInstance(SqlSession sqlSession): T
        # newInstance(MapperProxy mapperProxy): T
    }

    class MapperProxy {
        --
        + invoke(Object proxy, Method method, Object[] args): Object
    }

    class MapperMethod {
        --
        + execute(SqlSession sqlSession, Object[] args): Object
    }

    MapperProxyFactory ..> MapperProxy: new MapperProxy()
    MapperProxy ..> MapperMethod: mapperMethod.execute()
}

namespace org.apache.ibatis.executor #EEE {

    interface Executor {
        --
        + update(MappedStatement ms, Object parameter): int
        + query(MappedStatement ms, \nObject parameter, \nRowBounds rowBounds, \nResultHandler resultHandler, \nCacheKey cacheKey, \nBoundSql boundSql): List
        + commit(boolean required): void
        + rollback(boolean required): void
        + getTransaction(): Transaction
        + close(boolean forceRollback): void
    }

    org.apache.ibatis.binding.MapperMethod --> Executor

}

namespace org.apache.ibatis.plugin #EEE {

    interface Interceptor {
        --
        + intercept(Invocation invocation): Object
        + plugin(Object target): Object
        + setProperties(Properties properties): void
    }

    class Plugin {
        - target: Object
        - interceptor: Interceptor
        - signatureMap: Map, Set>
        --
        + @Override invoke(Object proxy, Method method, Object[] args): Object
    }

    javax.lang.reflect.InvocationHandler <|-- Plugin
    Plugin <. Interceptor: Plugin.wrap
}

namespace org.apache.ibatis.transaction #EEE {

    interface Transaction {
        --
        + getConnection(): Connection
        + commit(): void
        + rollback(): void
        + close(): void
    }

    interface TransactionFactory {
        --
        + newTransaction(Connection conn): Transaction
        + newTransaction(DataSource dataSource, \n TransactionIsolationLevel level, boolean autoCommit): Transaction
    }

    javax.sql.Connection <.. Transaction: getConnection()
    Transaction <.. TransactionFactory: newTransaction()
}

namespace org.apache.ibatis.mapping #EEE {

    class Environment {
        - id: String
        - transactionFactory: TransactionFactory
        - dataSource: DataSource
    }

    interface DatabaseIdProvider {
        --
        setProperties(Properties p): void
        getDatabaseId(DataSource dataSource): String
    }

    class VendorDatabaseIdProvider {
        --
        + @Override setProperties(Properties p): void
        + @Override getDatabaseId(DataSource dataSource): String
        - getDatabaseName(DataSource dataSource): String
    }

    DatabaseIdProvider <|.. VendorDatabaseIdProvider
}

'mybatis
namespace org.mybatis #EEE {

    class SqlSessionFactoryBean {
        - configLocation: Resource
        - configuration: Configuration
        - mapperLocations: Resource[]
        - dataSource: DataSource
        - transactionFactory: TransactionFactory
        - sqlSessionFactoryBuilder: SqlSessionFactoryBuilder
        - sqlSessionFactory: SqlSessionFactory
        - plugins: Interceptor[]
        - databaseIdProvider: DatabaseIdProvider
        --
        + @Override afterPropertiesSet(): void
        + @Override getObject(): SqlSessionFactory
        + @Override onApplicationEvent(ApplicationEvent event): void
    }
    note left of SqlSessionFactoryBean
        入口核心
        this.sqlSessionFactory = buildSqlSessionFactory()
    end note

    org.springframework.beans.factory.FactoryBean <|.. SqlSessionFactoryBean
    org.springframework.beans.factory.InitializingBean <|.. SqlSessionFactoryBean
    org.springframework.context.ApplicationListener <|.. SqlSessionFactoryBean
}


@enduml

写在后面

  如果本文内容对您有价值或者有启发的话,欢迎点赞、关注、评论和转发。您的反馈和陪伴将促进我们共同进步和成长。


系列文章

【UML】- 类图
【GitHub】- design-pattern(设计模式)
【连接池】-从源码到适配(上),你遇到过数据库连接池的问题吗?This connection has been closed
【连接池】-从源码到适配(下)使用dynamic-datasource导致连接池没生效(升级版本)

你可能感兴趣的:(看图说话,工作指北,mybatis,阅读源码,连接池,动态数据源,plantuml)