写在前面
前段时间做过一个项目,期间用到了动态数据源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。
三、动态数据源部分
(一)动态数据源加载、连接池创建
首先找到 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<DataSourceCreator> 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脚本在附录部分
有了类图就很明显了,这里面有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-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脚本在附录部分
(二)MapperProxy初始化
我们知道接口是无法执行方法的,这里MyBatis把所有的mapperInterface代理成了MapperProxy。以下是MapperProxy的创建过程活动图:
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分
(三)连接池的使用、数据库厂商加载、拦截器生效
以下是MyBatis操作整体流程活动图,
温馨提示:鼠标右键-》在新标签页中打开图片可查看高清图。PlantUML脚本在附录部分
我们以一个示例 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脚本在附录部分
五、收获
- 面向接口编程,抽象类实现通用逻辑,也可以通过模板的方式实现通用逻辑
- 一些自动配置,我们可以通过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<String, DataSourceProperty> 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<DataSourceCreator> dataSourceCreators)\n<color red> 与DataSourceCreator(dbcpCreator)绑定关系
dds_creator_a_config -> dds_creator: new DefaultDataSourceCreator()
dds_creator --> dds_creator_a_config: <color red>提供了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: <color red>提供了 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<DynamicDataSourcePropertiesCustomizer>
ddsa_config -> ddsa_config: DynamicDataSourceAutoConfiguration(properties, dataSourcePropertiesCustomizers)\n <color red>目的是拿到yml中的配置为其他Bean传参
end
group 创建YmlDynamicDataSourceProvider
ddsa_config -> ydds_provider : ymlDynamicDataSourceProvider(),\n new对象把datasource这个Map(每一个数据源)作为构造参数传入, <color red>相当于和dataSourcePropertiesMap进行了绑定
ydds_provider -> ads_provider: @Autowigreen DefaultDataSourceCreator defaultDataSourceCreator\n属性注入<color red>相当于和creator进行了绑定
ads_provider --> ydds_provider
ydds_provider --> ddsa_config: <color red>提供loadDataSources()方法
end
group 创建 DynamicRoutingDataSource 路由
ddsa_config -> drds: dataSource(),\n new对象,依据DynamicDataSourceProperties的属性设置自身,比如primary属性
group @Autowired属性注入providers
drds -> drds: @Autowired List<DynamicDataSourceProvider> providers: <color red>此处的provider就是创建YmlDynamicDataSourceProvider
end
drds --> ddsa_config: <color red>由于实现了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: <color red> __abstract doCreateDataSource(dataSourceProperty)__\n <color red>由连接池实现,以dbcp2为例
ads_creator -> dbcp2ds_creator: doCreateDataSource(DataSourceProperty dataSourceProperty)
dbcp2ds_creator -> basic_ds: start()
group #EEE 真正创建连接池
basic_ds -> basic_ds: createDataSource()
basic_ds -> basic_ds: <color red>for循环连接池initialSize\n <color red>connectionPool.addObject()\r <color red>添加到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<Interceptor[]>\n ResourceLoader\n ObjectProvider<DatabaseIdProvider>\n ObjectProvider<List<ConfigurationCustomizer)
end
group #EEE 创建 SqlSessionFactory(DataSource dataSource)
mba_config -> 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<color red>//处理configuration\n<color red>//addInterceptor(plugin)\n<color red>//setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource))\n<color red>//创建SpringManagedTransactionFactory\n<color red>//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: <color red>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<color red>//隐藏了一个代理\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<T> 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()\n<color red>getSqlSession().getMapper(this.mapperInterface)
mf_bean -> ss_template: getMapper(this.mapperInterface)
ss_template -> ss_template: getMapper(Class<T> type)\n//getConfiguration().getMapper(type, <color red>this</color>)
ss_template -> config: getMapper(Class<T> type, SqlSession sqlSession)
config -> mregistry: getMapper(Class<T> type, SqlSession sqlSession)
mregistry -> mp_factory:(MapperProxyFactory<T>) knownMappers.get(type)
mp_factory -> mproxy: newInstance(sqlSession):\n<color red>new MapperProxy<T>(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 <color red>// 难点:sqlSession.selectList(),\n <color red>此处的sqlSession是SqlSessionTemplate\n <color red>// 调用 this.sqlSessionProxy.<E> selectList(statement, parameter)\n <color red> 此处的 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: <color red>创建tx\ntransactionFactory.newTransaction
sm_transaction --> dss_factory
dss_factory -> config: <color red>创建Executor\nconfiguration.newExecutor(tx, execType)
group #EEE 创建 Executor
config -> config: 根据 executorTyp\n初始化Batch/Reuse/Simple/CachingExecutor
config -> interceptor_chain: (Executor) pluginAll(executor)\n<color red>包括PageInterceptor、MetaObjectInterceptor等
interceptor_chain -> interceptor: interceptor.plugin(target)
interceptor -> interceptor: plugin(Object executor)
interceptor -> plugin: <color red>Plugin.wrap(target, this): 代理对象,\n<color red>执行的handler是各类interceptor
plugin --> config
end
config --> dss_factory
dss_factory -> ds_session: <color red>创建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<color red>获取到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<T> iface): <T> 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<String, DataSource>
}
abstract class AbstractDataSourceProvider {
- defaultDataSourceCreator: DefaultDataSourceCreator
- dynamicDataSourceProperties: DynamicDataSourceProperties
--
# createDataSourceMap(Map<String, DataSourceProperty> dataSourcePropertiesMap): Map<String, DataSource>
}
class YmlDynamicDataSourceProvider {
- dataSourcePropertiesMap: Map<String, DataSourceProperty>
--
+ @Override loadDataSources(): Map<String, DataSource>
}
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<DataSourceCreator>
--
+ createDataSource(DataSourceProperty dataSourceProperty): DataSource
}
class BasicDataSourceCreator {
- creators: List<DataSourceCreator>
--
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<? extends DataSource>
- 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<String, DataSourceProperty>
- druid: DruidConfig
- dbcp2: Dbcp2Config
...
}
note left of DynamicDataSourceProperties::PREFIX
value = springframework.datasource.dynamic
end note
class DynamicDataSourceCreatorAutoConfiguration {
--
+ dbcp2DataSourceCreator(): Dbcp2DataSourceCreator
+ basicDataSourceCreator(): BasicDataSourceCreator
+ dataSourceCreator(List<DataSourceCreator> dataSourceCreators): DefaultDataSourceCreator
}
class DynamicDataSourceAutoConfiguration {
- properties: DynamicDataSourceProperties
- dataSourcePropertiesCustomizers: List<DynamicDataSourcePropertiesCustomizer>
--
+ 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<T> requiredType): T
+ isSingleton(String name): boolean
+ isPrototype(String name): boolean
}
interface FactoryBean<T> {
--
+ getObject(): T
}
interface InitializingBean {
+ {static} afterPropertiesSet(): void
}
}
'ibatis
namespace org.apache.ibatis #EEE {
interface SqlSession {
--
+ getMapper(Class<T> type): T
+ getConfiguration(): Configuration
+ getConnection(): Connection
+ selectOne(String statement): T
+ selectList(String statement): List<E>
+ 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<String, MappedStatement>
}
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<T>
- methodCache: Map<Method, MapperMethod>
--
+ newInstance(SqlSession sqlSession): T
# newInstance(MapperProxy<T> 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<E>
+ 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<Class<?>, Set<Method>>
--
+ @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导致连接池没生效(升级版本)