【源码】-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。


三、动态数据源部分

在这里插入图片描述

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

首先找到 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导致连接池没生效(升级版本)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/286129.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新网域名外部入库流程

注册商是新网&#xff0c;且在新网管理的&#xff0c;请使用此教程外部入库。 如您的域名注册商是新网但在聚名管理&#xff0c;请参考教程&#xff1a;https://www.west.cn/faq/list.asp?unid2539 在外部入库操作之前&#xff0c;请先登录新网获取用户ID和绑定邮箱信息。…

【LeetCode:LCR 143. 子结构判断 | 二叉树 + 递归】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

python调用openai api报错self._sslobj.do_handshake()OSError: [Errno 0] Error

python调用openai api报错self._sslobj.do_handshake()OSError: [Errno 0] Error 废话不说&#xff0c;先上代码&#xff0c;根据官网的介绍写的,chatgpt3.5 api简单调用 import os from openai import OpenAI from dotenv import load_dotenv# 加载 .env 文件中的变量 load_…

2023年下半年信息系统项目管理师考试

目录 引言 结果 论文 案例分析 综合知识 总结 引言 2023年下半年参加了信息系统项目管理师考试&#xff0c;考试结果情理之中&#xff0c;意料之外。论文压线&#xff0c;综合和案例差一分。从个人参加考试的整个过程来看&#xff0c;属于历史性的突破。以本文&#xff…

Linux 命令tail

命令作用 tail 命令用于显示文件的末尾内容&#xff0c;默认显示文件的最后 10 行。通常情况下&#xff0c;tail 命令用于实时查看动态日志文件&#xff0c;可以使用 -f 参数跟踪文件内容的变化。 语法 tail [选项] [文件名] 参数 以 log.txt 为例演示参数效果 -n -linesK…

生成模型 | GAN系列生成系列论文及代码调研总结

-------------✨ 生成模型 相关系列直达 ✨ ------------------------------------- &#x1fae7; GAN | 代码简单实现生成对抗网络&#xff08;GAN&#xff09;&#xff08;PyTorch&#xff09;_gan网络代码-CSDN博客 &#x1fae7; 生成模型 | GAN系列生成系列论文及代码调研…

java 打印日志的几种方式

java 打印日志的几种方式 Java 日志框架进化史日志门面与日志系统 Log4jslf4jLog4j2slf4jLogbackslf4j 一、先简单介绍五种 &#xff08;1&#xff09;最简单的方式&#xff0c;就是system.println.out(error) ,这样直接在控制台打印消息了&#xff1b; &#xff08;2&#xff…

python实现Ethernet/IP协议的客户端(二)

Ethernet/IP是一种工业自动化领域中常用的网络通信协议&#xff0c;它是基于标准以太网技术的应用层协议。作为工业领域的通信协议之一&#xff0c;Ethernet/IP 提供了一种在工业自动化设备之间实现通信和数据交换的标准化方法。python要实现Ethernet/IP的客户端&#xff0c;可…

自学软件测试?一般人我劝你回头是岸。。。

本人7年测试经验&#xff0c;在学测试之前对电脑的认知也就只限于上个网&#xff0c;玩个办公软件。这里不能跑题&#xff0c;我为啥说&#xff1a;自学软件测试&#xff0c;一般人我还是劝你算了吧&#xff1f;因为我就是那个一般人&#xff01; 软件测试基础真的很简单&…

单片机开发--keil5

一.keil5 Keil uVision5是一个集成开发环境&#xff08;IDE&#xff09;&#xff0c;用于对嵌入式系统中的微控制器进行编程。它是一个软件套件&#xff0c;包括源代码编辑器、项目经理、调试器以及微控制器开发、调试和编程所需的其他工具。Keil uVision5 IDE主要用于对基于A…

SpringBootWeb请求响应

请求 简单参数 在向服务器发起请求时&#xff0c;向服务器传递的是一些普通的请求数据。 原始方式知道原理即可&#xff0c;实际开发不会采用 在原始的Web程序当中&#xff0c;需要通过Servlet中提供的API&#xff1a;HttpServletRequest&#xff08;请求对象&#xff09;…

IP tables防火墙(一)

本章主要介绍&#xff1a; 熟悉Linux防火墙的表&#xff0c;链的结构理解数据包匹配的基本流程学会编写IP tables规则 1.0防火墙基础 在 Internet 中&#xff0c;企业通过架设各种应用系统来为用户提供各种网络服务&#xff0c;如 Web 网站、电子邮件系统、FTP 服务器、数…

eureka注册列表 某服务出现多个服务实例

最近文件导出功能偶发成功&#xff0c;大部分情况都失败&#xff0c;开始以为接口被拦截&#xff0c;gateway服务没有接口调用日志&#xff0c;发现测试环境可以&#xff0c;正式环境功能无法正常使用。 偶然看到注册中心如下 发现file服务有3个实例&#xff0c;调用接口将错误…

【OpenCV】OpenCV:计算机视觉的强大工具库

摘要   OpenCV是一个广泛应用于计算机视觉领域的开源工具库&#xff0c;为开发者提供了丰富的图像处理和计算机视觉算法。本文将介绍OpenCV的功能和应用领域&#xff0c;并探讨它在实践中的重要性和前景。 计算机视觉的强大工具库 一、什么是OpenCV&#xff1f;二、OpenCV的功…

Spring04

一、AOP的概念 AOP 为 (Aspect Oriented Programming) 的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;底层是使用动态代理的技术实现对目标方法的增强和控制访问等功能。 其中AOP中有几个重要的概念: 1、通知:增强的逻辑&#xff0c;或者后期要加入的代码。 2、目…

介绍混沌工程的主要作用是什么?

混沌工程是一种通过有序的混沌实验来提高系统稳健性和可靠性的实践。它不仅有助于发现问题&#xff0c;更能够促使团队建立起一种主动预防和处理问题的文化&#xff0c;从而为复杂系统的管理提供了全新的视角。本文将介绍混沌工程的主要作用&#xff1a; 发现系统脆弱性&#x…

中国社科院与新加坡新跃社科大联合培养博士——读博就业前景

现在国家高度重视高素质人才的培养&#xff0c;硕士和博士人数增多。很多人想要走学术道路&#xff0c;想要做自己喜欢领域的科研&#xff0c;不过也有很多人希望通过高学历获得更高的薪资待遇。根据一些数据表明我国每年的博士人数都在五万或六万左右。2018年的博士毕业生总人…

MidTool图文创作-GPT-4与DALL·E 3的结合

GPT-4与DALLE 3的结合 GPT-4是由OpenAI开发的最新一代语言预测模型&#xff0c;它在前代模型的基础上进行了大幅度的改进&#xff0c;不仅在文本生成的连贯性、准确性上有了显著提升&#xff0c;还在理解复杂语境和执行多步骤指令方面表现出了更高的能力。而DALLE 3则是一个创…

Mysql的主从复制和读写分离

一.主从复制 主从复制的作用&#xff1a; 读写分离 读写分离 具有高可用性 主从复制的工作过程&#xff1a; 主服务器master发生数据变化时&#xff0c;会记录到二进制日志中&#xff1b;从服务器slave会开启io线程请求二进制日志事件&#xff1b;主服务器会为每个io线程…

QT登录功能开发

登录功能 1选择无按钮的dialog 2登录函数 #include <QApplication> #include <QDialog> #include <QFormLayout> #include <QLineEdit> #include <QPushButton> #include <QMessageBox>class LoginDialog : public QDialog { public:Log…