注:本文转自:https://www.toutiao.com/article/7204651968885686787/?log_from=4fd44355ebbb6_1679021148713
背景
在高并发的项目中,单数据库已无法承载大数据量的访问,因此需要使用多个数据库进行对数据的读写分离,此外就是在微服化的今天,我们在项目中可能采用各种不同存储,因此也需要连接不同的数据库,居于这样的背景,这里简单分享实现的思路以及实现方案。
如何实现
多数据源实现思路有两种,一种是通过配置多个SqlSessionFactory实现多数据源;
另外一种是通过Spring提供的AbstractRoutingDataSource抽象了一个DynamicDataSource实现动态切换数据源;
实现方案
准备
采用Spring Boot2.7.8框架,数据库Mysql,ORM框架采用Mybatis,整个Maven依赖如下:
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <spring-boot.version>2.7.8</spring-boot.version> <mysql-connector-java.version>5.1.46</mysql-connector-java.version> <mybatis-spring-boot-starter.version>2.0.0</mybatis-spring-boot-starter.version> <mybatis.version>3.5.1</mybatis.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector-java.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot-starter.version}</version> </dependency> </dependencies> </dependencyManagement>
指定数据源操作指定目录XML文件
该种方式需要操作的数据库的Mapper层和Dao层分别建立一个文件夹,分包放置,整体项目结构如下图:
Maven依赖如下:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>4.0.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> </dependencies>
Yaml文件
spring: datasource: user: jdbc-url: jdbc:mysql://127.0.0.1:3306/study_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource #hikari连接池配置 hikari: #pool name pool-name: user #最小空闲连接数 minimum-idle: 5 #最大连接池 maximum-pool-size: 20 #链接超时时间 3秒 connection-timeout: 3000 # 连接测试query connection-test-query: SELECT 1 soul: jdbc-url: jdbc:mysql://127.0.0.1:3306/soul?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource #hikari连接池配置 hikari: #pool name pool-name: soul #最小空闲连接数 minimum-idle: 5 #最大连接池 maximum-pool-size: 20 #链接超时时间 3秒 connection-timeout: 3000 # 连接测试query connection-test-query: SELECT 1
不同库的Mapper指定不同的SqlSessionFactory
针对不同的库分别放置对用不同的SqlSessionFactory
@Configuration @MapperScan(basePackages = "org.datasource.demo1.usermapper", sqlSessionFactoryRef = "userSqlSessionFactory") public class UserDataSourceConfiguration { public static final String MAPPER_LOCATION = "classpath:usermapper/*.xml"; @Primary @Bean("userDataSource") @ConfigurationProperties(prefix = "spring.datasource.user") public DataSource userDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "userTransactionManager") @Primary public PlatformTransactionManager userTransactionManager(@Qualifier("userDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Primary @Bean(name = "userSqlSessionFactory") public SqlSessionFactory userSqlSessionFactory(@Qualifier("userDataSource") DataSource dataSource) throws Exception { final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(UserDataSourceConfiguration.MAPPER_LOCATION)); return sessionFactoryBean.getObject(); } } @Configuration @MapperScan(basePackages = "org.datasource.demo1.soulmapper", sqlSessionFactoryRef = "soulSqlSessionFactory") public class SoulDataSourceConfiguration { public static final String MAPPER_LOCATION = "classpath:soulmapper/*.xml"; @Bean("soulDataSource") @ConfigurationProperties(prefix = "spring.datasource.soul") public DataSource soulDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "soulTransactionManager") public PlatformTransactionManager soulTransactionManager(@Qualifier("soulDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "soulSqlSessionFactory") public SqlSessionFactory soulSqlSessionFactory(@Qualifier("soulDataSource") DataSource dataSource) throws Exception { final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SoulDataSourceConfiguration.MAPPER_LOCATION)); return sessionFactoryBean.getObject(); } }
使用
@Service public class AppAuthService { @Autowired private AppAuthMapper appAuthMapper; @Transactional(rollbackFor = Exception.class) public int getCount() { int a = appAuthMapper.listCount(); int b = 1 / 0; return a; } } @SpringBootTest @RunWith(SpringRunner.class) public class TestDataSource { @Autowired private AppAuthService appAuthService; @Autowired private SysUserService sysUserService; @Test public void test_dataSource1(){ int b=sysUserService.getCount(); int a=appAuthService.getCount(); } }
总结
此种方式使用起来分层明确,不存在任何冗余代码,不足地方就是每个库都需要对应一个配置类,该配置类中实现方式都基本类似,该种解决方案每个配置类中都存在事务管理器,因此不需要单独再去额外的关注。
AOP+自定义注解
关于采用Spring AOP方式实现原理就是把多个数据源存储在一个 Map中,当需要使用某个数据源时,从 Map中获取此数据源进行处理。
AbstractRoutingDataSource
在Spring中提供了AbstractRoutingDataSource来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法就可以完成数据源切换,该方法只需要返回数据源key即可,也就是存放数据源的Map的key,接下来我们来看一下AbstractRoutingDataSource整体的继承结构,看他是如何做到的。
在整体的继承结构上我们会发现AbstractRoutingDataSource最终是继承于DataSource,因此当我们继承AbstractRoutingDataSource是我们自身也是一个数据源,对于数据源必然有连接数据库的动作,如下代码:
public Connection getConnection() throws SQLException { return this.determineTargetDataSource().getConnection(); } public Connection getConnection(String username, String password) throws SQLException { return this.determineTargetDataSource().getConnection(username, password); }
只是AbstractRoutingDataSource的getConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。
protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } else { return dataSource; } }
该方法通过determineCurrentLookupKey()方法获取一个key,通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个Map<Object, DataSource>,里面应该保存当前所有可切换的数据源,接下来我们来聊聊实现,我们首先来看下目录,与分包的不同的是将所有的Mapper文件都放到一起,其他Maven依赖以及配置文件都保持一致。
DataSourceType
该枚举用来存放数据源的名称,
public enum DataSourceType { USERDATASOURCE("userDataSource"), SOULDATASOURCE("soulDataSource"); private String name; DataSourceType(String name) { this.name=name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
DynamicDataSourceConfiguration
通过读取配置文件中的数据源配置信息,创建数据连接,将多个数据源放入Map中,注入到容器中:
@Configuration @MapperScan(basePackages = "org.datasource.demo2.mapper") public class DynamicDataSourceConfiguration { @Primary @Bean(name = "userDataSource") @ConfigurationProperties(prefix = "spring.datasource.user") public DataSource userDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "soulDataSource") @ConfigurationProperties(prefix = "spring.datasource.soul") public DataSource soulDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") public DynamicDataSource DataSource(@Qualifier("userDataSource") DataSource userDataSource, @Qualifier("soulDataSource") DataSource soulDataSource) { //targetDataSource 集合是我们数据库和名字之间的映射 Map<Object, Object> targetDataSource = new HashMap<>(); targetDataSource.put(DataSourceType.USERDATASOURCE.getName(), userDataSource); targetDataSource.put(DataSourceType.SOULDATASOURCE.getName(), soulDataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSource); //设置默认对象 dataSource.setDefaultTargetDataSource(userDataSource); return dataSource; } @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setTransactionFactory(new MultiDataSourceTransactionFactory()); bean.setDataSource(dynamicDataSource); //设置我们的xml文件路径 bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources( "classpath*:mapper/*.xml")); return bean.getObject(); } }
DataSourceContext
DataSourceContext使用ThreadLocal存放当前线程使用的数据源类型信息;
public class DataSourceContext { private final static ThreadLocal<String> LOCAL_DATASOURCE = new ThreadLocal<>(); public static void set(String name) { LOCAL_DATASOURCE.set(name); } public static String get() { return LOCAL_DATASOURCE.get(); } public static void remove() { LOCAL_DATASOURCE.remove(); } }
DynamicDataSource
DynamicDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法,可以选择对应Key;
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContext.get(); } }
CurrentDataSource
定义数据源的注解;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface CurrentDataSource { DataSourceType value() default DataSourceType.USERDATASOURCE; }
DataSourceAspect
定义切面切点,用来切换数据源,
@Aspect @Order(-1) @Component public class DataSourceAspect { @Pointcut("@annotation(org.datasource.demo2.constant.CurrentDataSource)") public void dsPointCut() { } @Around("dsPointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); CurrentDataSource dataSource = method.getAnnotation(CurrentDataSource.class); if (Objects.nonNull(dataSource)) { System.out.println("切换数据源为" + dataSource.value().getName()); DataSourceContext.set(dataSource.value().getName()); } try { return point.proceed(); } finally { // 销毁数据源 在执行方法之后 System.out.println("销毁数据源" + dataSource.value().getName()); DataSourceContext.remove(); } } }
多数据源切换以后事务问题
Spring使用事务的方式有两种,一种是声明式事务,一种是编程式事务,我们讨论的都是关于声明式事务,这种方式很方便,也是大家常用的,这里为什么讨论这个问题,当我们想将不同库的表放在同一个事务使用的时候,这个是时候我们会报错,如下图:
这部分也就是其他技术贴没讲解的部分,因此这里我们来补充一下这个话题,背过八股们的小伙伴都知道Spring事务是居于AOP实现,从这个角度很容易会理解到这个问题,当我们将两个Service方法放在同一个Transactional下的时候,这个代理对象就是当前类,因此导致数据源对象也是当前类下的DataSource,导致就出现表不存在问题,当Transactional分别放在不同Service的时候没有这种情况。
@Transactional(rollbackFor = Exception.class) public void update(){ 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处理。
public class MultiDataSourceTransaction implements Transaction { private final DataSource dataSource; private ConcurrentMap<String, Connection> concurrentMap; private boolean autoCommit; public MultiDataSourceTransaction(DataSource dataSource) { this.dataSource = dataSource; concurrentMap = new ConcurrentHashMap<>(); } @Override public Connection getConnection() throws SQLException { String databaseIdentification = DataSourceContext.get(); if (StringUtils.isEmpty(databaseIdentification)) { databaseIdentification = DataSourceType.USERDATASOURCE.getName(); } //获取数据源 if (!this.concurrentMap.containsKey(databaseIdentification)) { try { Connection conn = this.dataSource.getConnection(); autoCommit=false; conn.setAutoCommit(false); this.concurrentMap.put(databaseIdentification, conn); } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could bot get JDBC otherConnection", ex); } } return this.concurrentMap.get(databaseIdentification); } @Override public void commit() throws SQLException { for (Connection connection : concurrentMap.values()) { if (!autoCommit) { connection.commit(); } } } @Override public void rollback() throws SQLException { for (Connection connection : concurrentMap.values()) { connection.rollback(); } } @Override public void close() throws SQLException { for (Connection connection : concurrentMap.values()) { DataSourceUtils.releaseConnection(connection, this.dataSource); } } @Override public Integer getTimeout() throws SQLException { return null; } } public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory { @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { return new MultiDataSourceTransaction(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对象。然后将执行TransactionFactory#newTransaction() 方法,初始化 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开启事务时候创建的对象,这样就保证了Spring Transaction中的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的方式,整体代码如下:
public class DynamicDataSourceBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware { /** * 默认dataSource */ private DataSource defaultDataSource; /** * 数据源map */ private Map<String, DataSource> dataSourcesMap = new HashMap<>(); @Override public void setEnvironment(Environment environment) { initConfig(environment); } private void initConfig(Environment env) { //读取配置文件获取更多数据源 String dsNames = env.getProperty("spring.datasource.names"); for (String dsName : dsNames.split(",")) { HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setPoolName(dsName); hikariConfig.setDriverClassName(env.getProperty("spring.datasource." + dsName.trim() + ".driver-class-name")); hikariConfig.setJdbcUrl(env.getProperty("spring.datasource." + dsName.trim() + ".jdbc-url")); 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.connection-timeout")))); hikariConfig.setMinimumIdle(Integer.parseInt(Objects.requireNonNull(env.getProperty("spring.datasource." + dsName.trim() + ".hikari.minimum-idle")))); hikariConfig.setMaximumPoolSize(Integer.parseInt(Objects.requireNonNull(env.getProperty("spring.datasource." + dsName.trim() + ".hikari.maximum-pool-size")))); hikariConfig.setConnectionInitSql("SELECT 1"); HikariDataSource dataSource = new HikariDataSource(hikariConfig); if (dataSourcesMap.size() == 0) { defaultDataSource = dataSource; } dataSourcesMap.put(dsName, dataSource); } } @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); //添加其他数据源 targetDataSources.putAll(dataSourcesMap); //创建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); //defaultTargetDataSource 和 targetDataSources属性是 AbstractRoutingDataSource的两个属性Map mpv.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}) public @interface EnableDynamicDataSource { } @SpringBootApplication @EnableAspectJAutoProxy @EnableDynamicDataSource public class DataSourceApplication { public static void main(String[] args) { SpringApplication.run(DataSourceApplication.class, args); } }
DynamicDataSourceConfig
该类负责将Mapper扫描以及SpringFactory定义;
@Configuration @MapperScan(basePackages = "org.datasource.demo3.mapper") public class DynamicDataSourceConfig { @Autowired private DataSource dynamicDataSource; @Bean(name = "sqlSessionFactory") public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setTransactionFactory(new MultiDataSourceTransactionFactory()); bean.setDataSource(dynamicDataSource); //设置我们的xml文件路径 bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources( "classpath*:mapper/*.xml")); return bean.getObject(); } }
yaml
关于yaml部分我们增加了names定义,方便识别出来配置了几个DataSource,剩下的部分与AOP保持一致。
spring: datasource: names: user,soul user: jdbc-url: jdbc:mysql://127.0.0.1:3306/study_user?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource #hikari连接池配置 hikari: #最小空闲连接数 minimum-idle: 5 #最大连接池 maximum-pool-size: 20 #链接超时时间 3秒 connection-timeout: 3000 soul: jdbc-url: jdbc:mysql://127.0.0.1:3306/soul?useSSL=false&useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource #hikari连接池配置 hikari: #最小空闲连接数 minimum-idle: 5 #最大连接池 maximum-pool-size: 20 #链接超时时间 3秒 connection-timeout: 3000