背景 在高并发的项目中,单数据库已无法承载大数据量的访问,因此需要使用多个数据库进行对数据的读写分离,此外就是在微服化的今天,我们在项目中可能采用各种不同存储,因此也需要连接不同的数据库,居于这样的背景,这里简单分享实现的思路以及实现方案。如何实现 多数据源实现思路有两种,一种是通过配置多个SqlSessionFactory实现多数据源; 另外一种是通过Spring提供的AbstractRoutingDataSource抽象了一个DynamicDataSource实现动态切换数据源; 实现方案准备 采用SpringBoot2。7。8框架,数据库Mysql,ORM框架采用Mybatis,整个Maven依赖如下:propertiesmaven。compiler。source8maven。compiler。sourcemaven。compiler。target8maven。compiler。targetspringboot。version2。7。8springboot。versionmysqlconnectorjava。version5。1。46mysqlconnectorjava。versionmybatisspringbootstarter。version2。0。0mybatisspringbootstarter。versionmybatis。version3。5。1mybatis。versionpropertiesdependencyManagementdependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootdependenciesartifactIdversion{springboot。version}versiontypepomtypescopeimportscopedependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIdversion{mysqlconnectorjava。version}versiondependencydependencygroupIdorg。mybatisgroupIdmybatisartifactIdversion{mybatis。version}versiondependencydependencygroupIdorg。mybatis。spring。bootgroupIdmybatisspringbootstarterartifactIdversion{mybatisspringbootstarter。version}versiondependencydependenciesdependencyManagement指定数据源操作指定目录XML文件 该种方式需要操作的数据库的Mapper层和Dao层分别建立一个文件夹,分包放置,整体项目结构如下图: Maven依赖如下:dependenciesdependencygroupIdorg。springframework。bootgroupIdspringbootstarterwebartifactIddependencydependencygroupIdorg。springframework。bootgroupIdspringbootstartertestartifactIddependencydependencygroupIdcom。zaxxergroupIdHikariCPartifactIdversion4。0。3versiondependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIddependencydependencygroupIdmysqlgroupIdmysqlconnectorjavaartifactIddependencydependencygroupIdorg。mybatisgroupIdmybatisartifactIddependencydependencygroupIdorg。mybatis。spring。bootgroupIdmybatisspringbootstarterartifactIddependencydependencygroupIdjunitgroupIdjunitartifactIdscopetestscopedependencydependenciesYaml文件spring:datasource:user:jdbcurl:jdbc:mysql:127。0。0。1:3306studyuser?useSSLfalseuseUnicodetruecharacterEncodingUTF8username:rootpassword:123456driverclassname:com。mysql。jdbc。Drivertype:com。zaxxer。hikari。HikariDataSourcehikari连接池配置hikari:poolnamepoolname:user最小空闲连接数minimumidle:5最大连接池maximumpoolsize:20链接超时时间3秒connectiontimeout:3000连接测试queryconnectiontestquery:SELECT1soul:jdbcurl:jdbc:mysql:127。0。0。1:3306soul?useSSLfalseuseUnicodetruecharacterEncodingUTF8username:rootpassword:123456driverclassname:com。mysql。jdbc。Drivertype:com。zaxxer。hikari。HikariDataSourcehikari连接池配置hikari:poolnamepoolname:soul最小空闲连接数minimumidle:5最大连接池maximumpoolsize:20链接超时时间3秒connectiontimeout:3000连接测试queryconnectiontestquery:SELECT1不同库的Mapper指定不同的SqlSessionFactory 针对不同的库分别放置对用不同的SqlSessionFactoryConfigurationMapperScan(basePackagesorg。datasource。demo1。usermapper,sqlSessionFactoryRefuserSqlSessionFactory)publicclassUserDataSourceConfiguration{publicstaticfinalStringMAPPERLOCATIONclasspath:usermapper。xml;PrimaryBean(userDataSource)ConfigurationProperties(prefixspring。datasource。user)publicDataSourceuserDataSource(){returnDataSourceBuilder。create()。build();}Bean(nameuserTransactionManager)PrimarypublicPlatformTransactionManageruserTransactionManager(Qualifier(userDataSource)DataSourcedataSource){returnnewDataSourceTransactionManager(dataSource);}PrimaryBean(nameuserSqlSessionFactory)publicSqlSessionFactoryuserSqlSessionFactory(Qualifier(userDataSource)DataSourcedataSource)throwsException{finalSqlSessionFactoryBeansessionFactoryBeannewSqlSessionFactoryBean();sessionFactoryBean。setDataSource(dataSource);sessionFactoryBean。setMapperLocations(newPathMatchingResourcePatternResolver()。getResources(UserDataSourceConfiguration。MAPPERLOCATION));returnsessionFactoryBean。getObject();}}ConfigurationMapperScan(basePackagesorg。datasource。demo1。soulmapper,sqlSessionFactoryRefsoulSqlSessionFactory)publicclassSoulDataSourceConfiguration{publicstaticfinalStringMAPPERLOCATIONclasspath:soulmapper。xml;Bean(soulDataSource)ConfigurationProperties(prefixspring。datasource。soul)publicDataSourcesoulDataSource(){returnDataSourceBuilder。create()。build();}Bean(namesoulTransactionManager)publicPlatformTransactionManagersoulTransactionManager(Qualifier(soulDataSource)DataSourcedataSource){returnnewDataSourceTransactionManager(dataSource);}Bean(namesoulSqlSessionFactory)publicSqlSessionFactorysoulSqlSessionFactory(Qualifier(soulDataSource)DataSourcedataSource)throwsException{finalSqlSessionFactoryBeansessionFactoryBeannewSqlSessionFactoryBean();sessionFactoryBean。setDataSource(dataSource);sessionFactoryBean。setMapperLocations(newPathMatchingResourcePatternResolver()。getResources(SoulDataSourceConfiguration。MAPPERLOCATION));returnsessionFactoryBean。getObject();}}使用ServicepublicclassAppAuthService{AutowiredprivateAppAuthMapperappAuthMapper;Transactional(rollbackForException。class)publicintgetCount(){intaappAuthMapper。listCount();intb10;returna;}}SpringBootTestRunWith(SpringRunner。class)publicclassTestDataSource{AutowiredprivateAppAuthServiceappAuthService;AutowiredprivateSysUserServicesysUserService;TestpublicvoidtestdataSource1(){intbsysUserService。getCount();intaappAuthService。getCount();}}总结 此种方式使用起来分层明确,不存在任何冗余代码,不足地方就是每个库都需要对应一个配置类,该配置类中实现方式都基本类似,该种解决方案每个配置类中都存在事务管理器,因此不需要单独再去额外的关注。AOP自定义注解 关于采用SpringAOP方式实现原理就是把多个数据源存储在一个Map中,当需要使用某个数据源时,从Map中获取此数据源进行处理。 AbstractRoutingDataSource 在Spring中提供了AbstractRoutingDataSource来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法就可以完成数据源切换,该方法只需要返回数据源key即可,也就是存放数据源的Map的key,接下来我们来看一下AbstractRoutingDataSource整体的继承结构,看他是如何做到的。 在整体的继承结构上我们会发现AbstractRoutingDataSource最终是继承于DataSource,因此当我们继承AbstractRoutingDataSource是我们自身也是一个数据源,对于数据源必然有连接数据库的动作,如下代码:publicConnectiongetConnection()throwsSQLException{returnthis。determineTargetDataSource()。getConnection();}publicConnectiongetConnection(Stringusername,Stringpassword)throwsSQLException{returnthis。determineTargetDataSource()。getConnection(username,password);} 只是AbstractRoutingDataSource的getConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。protectedDataSourcedetermineTargetDataSource(){Assert。notNull(this。resolvedDataSources,DataSourcerouternotinitialized);ObjectlookupKeythis。determineCurrentLookupKey();DataSourcedataSource(DataSource)this。resolvedDataSources。get(lookupKey);if(dataSourcenull(this。lenientFallbacklookupKeynull)){dataSourcethis。resolvedDefaultDataSource;}if(dataSourcenull){thrownewIllegalStateException(CannotdeterminetargetDataSourceforlookupkey〔lookupKey〕);}else{returndataSource;}} 该方法通过determineCurrentLookupKey()方法获取一个key,通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个MapObject,DataSource,里面应该保存当前所有可切换的数据源,接下来我们来聊聊实现,我们首先来看下目录,与分包的不同的是将所有的Mapper文件都放到一起,其他Maven依赖以及配置文件都保持一致。 DataSourceType 该枚举用来存放数据源的名称,publicenumDataSourceType{USERDATASOURCE(userDataSource),SOULDATASOURCE(soulDataSource);privateStringname;DataSourceType(Stringname){this。namename;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this。namename;}}DynamicDataSourceConfiguration 通过读取配置文件中的数据源配置信息,创建数据连接,将多个数据源放入Map中,注入到容器中:ConfigurationMapperScan(basePackagesorg。datasource。demo2。mapper)publicclassDynamicDataSourceConfiguration{PrimaryBean(nameuserDataSource)ConfigurationProperties(prefixspring。datasource。user)publicDataSourceuserDataSource(){returnDataSourceBuilder。create()。build();}Bean(namesoulDataSource)ConfigurationProperties(prefixspring。datasource。soul)publicDataSourcesoulDataSource(){returnDataSourceBuilder。create()。build();}Bean(namedynamicDataSource)publicDynamicDataSourceDataSource(Qualifier(userDataSource)DataSourceuserDataSource,Qualifier(soulDataSource)DataSourcesoulDataSource){targetDataSource集合是我们数据库和名字之间的映射MapObject,ObjecttargetDataSourcenewHashMap();targetDataSource。put(DataSourceType。USERDATASOURCE。getName(),userDataSource);targetDataSource。put(DataSourceType。SOULDATASOURCE。getName(),soulDataSource);DynamicDataSourcedataSourcenewDynamicDataSource();dataSource。setTargetDataSources(targetDataSource);设置默认对象dataSource。setDefaultTargetDataSource(userDataSource);returndataSource;}Bean(namesqlSessionFactory)publicSqlSessionFactorysqlSessionFactory(Qualifier(dynamicDataSource)DataSourcedynamicDataSource)throwsException{SqlSessionFactoryBeanbeannewSqlSessionFactoryBean();bean。setTransactionFactory(newMultiDataSourceTransactionFactory());bean。setDataSource(dynamicDataSource);设置我们的xml文件路径bean。setMapperLocations(newPathMatchingResourcePatternResolver()。getResources(classpath:mapper。xml));returnbean。getObject();}}DataSourceContext DataSourceContext使用ThreadLocal存放当前线程使用的数据源类型信息;publicclassDataSourceContext{privatefinalstaticThreadLocalStringLOCALDATASOURCEnewThreadLocal();publicstaticvoidset(Stringname){LOCALDATASOURCE。set(name);}publicstaticStringget(){returnLOCALDATASOURCE。get();}publicstaticvoidremove(){LOCALDATASOURCE。remove();}}DynamicDataSource DynamicDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法,可以选择对应Key;publicclassDynamicDataSourceextendsAbstractRoutingDataSource{OverrideprotectedObjectdetermineCurrentLookupKey(){returnDataSourceContext。get();}}CurrentDataSource 定义数据源的注解;Retention(RetentionPolicy。RUNTIME)Target(ElementType。METHOD)publicinterfaceCurrentDataSource{DataSourceTypevalue()defaultDataSourceType。USERDATASOURCE;}DataSourceAspect 定义切面切点,用来切换数据源,AspectOrder(1)ComponentpublicclassDataSourceAspect{Pointcut(annotation(org。datasource。demo2。constant。CurrentDataSource))publicvoiddsPointCut(){}Around(dsPointCut())publicObjectaround(ProceedingJoinPointpoint)throwsThrowable{MethodSignaturesignature(MethodSignature)point。getSignature();Methodmethodsignature。getMethod();CurrentDataSourcedataSourcemethod。getAnnotation(CurrentDataSource。class);if(Objects。nonNull(dataSource)){System。out。println(切换数据源为dataSource。value()。getName());DataSourceContext。set(dataSource。value()。getName());}try{returnpoint。proceed();}finally{销毁数据源在执行方法之后System。out。println(销毁数据源dataSource。value()。getName());DataSourceContext。remove();}}}多数据源切换以后事务问题 Spring使用事务的方式有两种,一种是声明式事务,一种是编程式事务,我们讨论的都是关于声明式事务,这种方式很方便,也是大家常用的,这里为什么讨论这个问题,当我们想将不同库的表放在同一个事务使用的时候,这个是时候我们会报错,如下图: 这部分也就是其他技术贴没讲解的部分,因此这里我们来补充一下这个话题,背过八股们的小伙伴都知道Spring事务是居于AOP实现,从这个角度很容易会理解到这个问题,当我们将两个Service方法放在同一个Transactional下的时候,这个代理对象就是当前类,因此导致数据源对象也是当前类下的DataSource,导致就出现表不存在问题,当Transactional分别放在不同Service的时候没有这种情况。Transactional(rollbackForException。class)publicvoidupdate(){sysUserMapper。updateSysUser(111);appAuthService。update(111);} 有没有办法解决这个问题呢,当然是有的,这里我就不一步一步去探讨源码问题,我就直接直捣黄龙,把问题本质说一下,在Spring事务管理中有一个核心类DataSourceTransactionManager,该类是Spring事务核心的默认实现,AbstractPlatformTransactionManager是整体的Spring事务实现模板类,整体的继承结构如下图, 在方案一中,我们针对每个DataSourece都创建对应的DataSourceTransactionManager实现,也可以看出DataSourceTransactionManager就是管理我们整体的事务的,当我们配置了事物管理器以及拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存DataSource、SqlSessionFactory、Connection,因为DataSource、Conneciton都是从缓存中拿的,因此我们怎么切换数据源也没用,因此就出现表不存在的报错,具体源码可参考下面截图部分: 看到这里我们大致明白了为什么会报错,那么我们该如何做才能实现这种情况呢?其实我们要做的事就是动态的根据DataSourceType获取不同的Connection,不从缓存中获取Connection。解决方案 我们来自定义一个MultiDataSourceTransaction实现Mybatis的事务接口,使用Map存储Connection相关连接,所有事务都采用手动提交,之后将MultiDataSourceTransaction交给SpringManagedTransactionFactory处理。publicclassMultiDataSourceTransactionimplementsTransaction{privatefinalDataSourcedataSource;privateConcurrentMapString,ConnectionconcurrentMap;privatebooleanautoCommit;publicMultiDataSourceTransaction(DataSourcedataSource){this。dataSourcedataSource;concurrentMapnewConcurrentHashMap();}OverridepublicConnectiongetConnection()throwsSQLException{StringdatabaseIdentificationDataSourceContext。get();if(StringUtils。isEmpty(databaseIdentification)){databaseIdentificationDataSourceType。USERDATASOURCE。getName();}获取数据源if(!this。concurrentMap。containsKey(databaseIdentification)){try{Connectionconnthis。dataSource。getConnection();autoCommitfalse;conn。setAutoCommit(false);this。concurrentMap。put(databaseIdentification,conn);}catch(SQLExceptionex){thrownewCannotGetJdbcConnectionException(CouldbotgetJDBCotherConnection,ex);}}returnthis。concurrentMap。get(databaseIdentification);}Overridepublicvoidcommit()throwsSQLException{for(Connectionconnection:concurrentMap。values()){if(!autoCommit){connection。commit();}}}Overridepublicvoidrollback()throwsSQLException{for(Connectionconnection:concurrentMap。values()){connection。rollback();}}Overridepublicvoidclose()throwsSQLException{for(Connectionconnection:concurrentMap。values()){DataSourceUtils。releaseConnection(connection,this。dataSource);}}OverridepublicIntegergetTimeout()throwsSQLException{returnnull;}}publicclassMultiDataSourceTransactionFactoryextendsSpringManagedTransactionFactory{OverridepublicTransactionnewTransaction(DataSourcedataSource,TransactionIsolationLevellevel,booleanautoCommit){returnnewMultiDataSourceTransaction(dataSource);}}为什么可以这么做 在Mybatis自动装配式会将配置文件装配为Configuration对象,也就是在方案一种SqlSessionFactory配置的过程,其中SqlSessionFactoryBean类实现了InitializingBean接口,初始化后执行afterPropertiesSet()方法,在afterPropertiesSet()方法中会执行BuildSqlSessionFactory()方法生成一个SqlSessionFactory对象。在BuildSqlSessionFactory中,会创建SpringManagedTransactionFactory对象,该对象就是MyBatis跟Spring的桥梁。 在MapperScan自动扫描Mapper过程中,会通过ClassPathMapperScanner扫描器找到Mapper接口,封装成各自的BeanDefinition,然后循环遍历对Mapper的BeanDefinition修改beanClass为MapperFactoryBean。 由于MapperFactoryBean实现了FactoryBean,在Bean生命周期管理时会调用getObject方法,通过JDK动态代理生成代理对象MapperProxy,Mapper接口请求的时候,执行MapperProxy代理类的invoke方法,执行的过程中通过SqlSessionFactory创建的SqlSession去调用Executor执行器,进行数据库操作。下图是SqlSession创建的整个过程: openSession方法是将Spring事务管理关联起来的核心代码,首先这里将通过getTransactionFactoryFromEnvironment()方法获取TransactionFactory。这个操作会得到初始化时候注入的SpringManagedTransactionFactory对象。然后将执行TransactionFactorynewTransaction()方法,初始化MyBatis的Transaction。 这里通过Configuration。newExecutor()创建一个Executor,Configuration指定在Executor默认为Simple,因此这里会创建一个SimpleExecutor,并初始化Transaction属性。接下来我们来看下SimpleExecutor执行执行update方法时候执行prepareStatement方法,在prepareStatement方法中执行了getConnection方法, 这里我们可以看到Connection获取过程,是通过Transaction获取的getConnection(),也就是通过之前注入的Transaction来获取Connection,这个Transaction就是SpringManagedTransaction,整体的时序图如下: 在整个调用链过程中,我们看到在DataSourceUtils有我们熟悉的TransactionSynchronizationManager,在上面Spring事务的时候我们也提到这个类,在开始Spring事务以后就会把Connetion绑定到当前线程,在DataSourceUtils获取到的Connection对象就是Srping开启事务时候创建的对象,这样就保证了SpringTransaction中的Connection跟MyBatis中执行SQL语句用的Connection为同一个Connection,也就可以通过Spring事务管理机制进行事务管理了。 明白了整个流程,我们要做的事也就很简单,也就是每次切换DataSoure的同时获取最新的Connection,然后用一个Map对象来记录整个过程中的Connection,出现回滚这个Map对象里面Connection对象都回滚就可以了,然后将我们自定义的Transaction,委托给Spring在进行管理。总结 采用AOP的方式是切换数据源已经非常好了,唯一不太好的地方就在于依然要手动去创建DataSource,每次增加都需要增加一个Bean,那有没有办法解决呢?当然是有的,让我们来更上一层楼,解放双手。更上一层楼ImportBeanDefinitionRegistrar ImportBeanDefinitionRegistrar接口是Spring提供一个扩展点,主要用来注册BeanDefinition,常见的第三方框架在集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到Spring容器中。比如Mybatis中的Mapper接口,SpringCloud中的Feignlient接口,都是通过该接口实现的自定义注册逻辑。我们要做的事情就是通过ImportBeanDefinitionRegistrar帮助我们动态的将DataSource扫描的到容器中去,不在采用增加Bean的方式,整体代码如下:publicclassDynamicDataSourceBeanDefinitionRegistrarimplementsImportBeanDefinitionRegistrar,EnvironmentAware{默认dataSourceprivateDataSourcedefaultDataSource;数据源mapprivateMapString,DataSourcedataSourcesMapnewHashMap();OverridepublicvoidsetEnvironment(Environmentenvironment){initConfig(environment);}privatevoidinitConfig(Environmentenv){读取配置文件获取更多数据源StringdsNamesenv。getProperty(spring。datasource。names);for(StringdsName:dsNames。split(,)){HikariConfighikariConfignewHikariConfig();hikariConfig。setPoolName(dsName);hikariConfig。setDriverClassName(env。getProperty(spring。datasource。dsName。trim()。driverclassname));hikariConfig。setJdbcUrl(env。getProperty(spring。datasource。dsName。trim()。jdbcurl));hikariConfig。setUsername(env。getProperty(spring。datasource。dsName。trim()。username));hikariConfig。setPassword(env。getProperty(spring。datasource。dsName。trim()。password));hikariConfig。setConnectionTimeout(Long。parseLong(Objects。requireNonNull(env。getProperty(spring。datasource。dsName。trim()。hikari。connectiontimeout))));hikariConfig。setMinimumIdle(Integer。parseInt(Objects。requireNonNull(env。getProperty(spring。datasource。dsName。trim()。hikari。minimumidle))));hikariConfig。setMaximumPoolSize(Integer。parseInt(Objects。requireNonNull(env。getProperty(spring。datasource。dsName。trim()。hikari。maximumpoolsize))));hikariConfig。setConnectionInitSql(SELECT1);HikariDataSourcedataSourcenewHikariDataSource(hikariConfig);if(dataSourcesMap。size()0){defaultDataSourcedataSource;}dataSourcesMap。put(dsName,dataSource);}}OverridepublicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){MapObject,ObjecttargetDataSourcesnewHashMapObject,Object();添加其他数据源targetDataSources。putAll(dataSourcesMap);创建DynamicDataSourceGenericBeanDefinitionbeanDefinitionnewGenericBeanDefinition();beanDefinition。setBeanClass(DynamicDataSource。class);beanDefinition。setSynthetic(true);MutablePropertyValuesmpvbeanDefinition。getPropertyValues();defaultTargetDataSource和targetDataSources属性是AbstractRoutingDataSource的两个属性Mapmpv。addPropertyValue(defaultTargetDataSource,defaultDataSource);mpv。addPropertyValue(targetDataSources,targetDataSources);注册registry。registerBeanDefinition(dataSource,beanDefinition);}}Import Import模式是向容器导入Bean是一种非常重要的方式,在注解驱动的Spring项目中,Enablexxx的设计模式中有大量的使用,我们通过ImportBeanDefinitionRegistrar完成Bean的扫描,通过Import导入到容器中,然后将EnableDynamicDataSource放入SpringBoot的启动项之上,到这里有没有感觉到茅塞顿开的感觉。Target({ElementType。TYPE})Retention(RetentionPolicy。RUNTIME)Import({DynamicDataSourceBeanDefinitionRegistrar。class})publicinterfaceEnableDynamicDataSource{}SpringBootApplicationEnableAspectJAutoProxyEnableDynamicDataSourcepublicclassDataSourceApplication{publicstaticvoidmain(String〔〕args){SpringApplication。run(DataSourceApplication。class,args);}}DynamicDataSourceConfig 该类负责将Mapper扫描以及SpringFactory定义;ConfigurationMapperScan(basePackagesorg。datasource。demo3。mapper)publicclassDynamicDataSourceConfig{AutowiredprivateDataSourcedynamicDataSource;Bean(namesqlSessionFactory)publicSqlSessionFactorysqlSessionFactory()throwsException{SqlSessionFactoryBeanbeannewSqlSessionFactoryBean();bean。setTransactionFactory(newMultiDataSourceTransactionFactory());bean。setDataSource(dynamicDataSource);设置我们的xml文件路径bean。setMapperLocations(newPathMatchingResourcePatternResolver()。getResources(classpath:mapper。xml));returnbean。getObject();}}yaml 关于yaml部分我们增加了names定义,方便识别出来配置了几个DataSource,剩下的部分与AOP保持一致。spring:datasource:names:user,souluser:jdbcurl:jdbc:mysql:127。0。0。1:3306studyuser?useSSLfalseuseUnicodetruecharacterEncodingUTF8username:rootpassword:123456driverclassname:com。mysql。jdbc。Drivertype:com。zaxxer。hikari。HikariDataSourcehikari连接池配置hikari:最小空闲连接数minimumidle:5最大连接池maximumpoolsize:20链接超时时间3秒connectiontimeout:3000soul:jdbcurl:jdbc:mysql:127。0。0。1:3306soul?useSSLfalseuseUnicodetruecharacterEncodingUTF8username:rootpassword:123456driverclassname:com。mysql。jdbc。Drivertype:com。zaxxer。hikari。HikariDataSourcehikari连接池配置hikari:最小空闲连接数minimumidle:5最大连接池maximumpoolsize:20链接超时时间3秒connectiontimeout:3000结束