# multi-datasource-demo **Repository Path**: earthchen/multi-datasource-demo ## Basic Information - **Project Name**: multi-datasource-demo - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2018-08-26 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # springboot使用aop进行双数据源切换 ## 环境搭建 创建一个spring boot项目,并引入druid mysql aop等相关依赖 ```xml com.baomidou mybatisplus-spring-boot-starter ${mybatisplus-spring-boot-starter.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop mysql mysql-connector-java com.baomidou mybatis-plus ${mybatis-plus.version} com.alibaba druid ${druid.version} org.springframework.boot spring-boot-starter-test test ``` ### 编写数据源注解 ```java import java.lang.annotation.*; /** * 多数据源标识 * * @author earthchen * @date 2018/8/26 **/ @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface TargetDataSource { String name() default ""; } ``` >使用aop切换数据源的规则就是被该注解标识的方法 ### 编写多数据源配置文件 ```java import com.alibaba.druid.pool.DruidDataSource; import com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor; import com.baomidou.mybatisplus.plugins.PaginationInterceptor; import com.baomidou.mybatisplus.plugins.PerformanceInterceptor; import com.earthchen.aop.MultiSourceAop; import com.earthchen.mutidatasource.DynamicDataSource; import com.earthchen.properites.DruidProperties; import com.earthchen.properites.MultiDataSourceProperties; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import java.sql.SQLException; import java.util.HashMap; /** * @author earthchen * @date 2018/8/26 **/ @Configuration @EnableTransactionManagement(order = 2) @MapperScan(basePackages = {"com.earthchen.dao"}) public class MultiDataSourceConfig { private Logger logger = LoggerFactory.getLogger(MultiDataSourceConfig.class); @Bean public MultiSourceAop multiSourceExAop() { return new MultiSourceAop(); } /** * guns的数据源 */ private DruidDataSource dataSource(DruidProperties druidProperties) { DruidDataSource dataSource = new DruidDataSource(); druidProperties.config(dataSource); logger.info("数据源1为{}", druidProperties.toString()); return dataSource; } /** * 多数据源,第二个数据源 */ private DruidDataSource bizDataSource(DruidProperties druidProperties, MultiDataSourceProperties multiDataSourceProperties) { DruidDataSource dataSource = new DruidDataSource(); druidProperties.config(dataSource); multiDataSourceProperties.config(dataSource); logger.info("数据源2为{}", multiDataSourceProperties.toString()); return dataSource; } /** * 多数据源连接池配置 */ @Bean public DynamicDataSource multiDataSource(DruidProperties druidProperties, MultiDataSourceProperties mutiDataSourceProperties) { DruidDataSource dataSourceGuns = dataSource(druidProperties); DruidDataSource bizDataSource = bizDataSource(druidProperties, mutiDataSourceProperties); try { dataSourceGuns.init(); bizDataSource.init(); } catch (SQLException sql) { sql.printStackTrace(); } DynamicDataSource dynamicDataSource = new DynamicDataSource(); HashMap hashMap = new HashMap<>(); // 将两个数据源加入map hashMap.put(mutiDataSourceProperties.getDataSourceNames()[0], dataSourceGuns); hashMap.put(mutiDataSourceProperties.getDataSourceNames()[1], bizDataSource); logger.info("两个数据源名字分别为{},{}", mutiDataSourceProperties.getDataSourceNames()[0], mutiDataSourceProperties.getDataSourceNames()[1]); dynamicDataSource.setTargetDataSources(hashMap); dynamicDataSource.setDefaultTargetDataSource(dataSourceGuns); return dynamicDataSource; } /** * mybatis-plus SQL执行效率插件【生产环境可以关闭】 * * @return */ @Bean public PerformanceInterceptor performanceInterceptor() { return new PerformanceInterceptor(); } /** * mybatis-plus分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); // 开启 PageHelper 的支持 paginationInterceptor.setLocalPage(true); return paginationInterceptor; } /** * 乐观锁mybatis插件 */ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } /** * 事务配置 * * @author stylefeng * @Date 2018/6/27 23:11 */ @Bean public DataSourceTransactionManager dataSourceTransactionManager(DynamicDataSource mutiDataSource) { return new DataSourceTransactionManager(mutiDataSource); } } ``` >- 这里配置了两个druid的datasource >- 核心方法为multiDataSource(),在这里把相关数据源加入一个map中,方便切换 这里依赖了druid的配置类和一个多数据源的配置类,我们需要在yml里编写合适的配置,也需要创建合适的类接受自定义配置 ```java import com.alibaba.druid.pool.DruidDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Arrays; /** * @author earthchen * @date 2018/8/26 **/ @Component @ConfigurationProperties(prefix = "multi-datasource") public class MultiDataSourceProperties { private String url = "jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull"; private String username = "root"; private String password = "123456"; private String driverClassName = "com.mysql.jdbc.Driver"; private String validationQuery = "SELECT 'x'"; private String[] dataSourceNames = {"dataSourceGuns", "dataSourceBiz"}; public void config(DruidDataSource dataSource) { dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); dataSource.setValidationQuery(validationQuery); } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getValidationQuery() { return validationQuery; } public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; } public String[] getDataSourceNames() { return dataSourceNames; } public void setDataSourceNames(String[] dataSourceNames) { this.dataSourceNames = dataSourceNames; } @Override public String toString() { return "MultiDataSourceProperties{" + "url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + ", driverClassName='" + driverClassName + '\'' + ", validationQuery='" + validationQuery + '\'' + ", dataSourceNames=" + Arrays.toString(dataSourceNames) + '}'; } } ``` > 负责接收多数据源配置 ```java import com.alibaba.druid.pool.DruidDataSource; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.sql.SQLException; /** * 数据库数据源配置 * 说明:这个类中包含了许多默认配置,若这些配置符合您的情况, * 您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可 * * @author earthchen * @date 2018/8/26 **/ @Component @ConfigurationProperties(prefix = "spring.datasource") public class DruidProperties { private String url = "jdbc:mysql://127.0.0.1:3306/test?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull"; private String username = "root"; private String password = "123456"; private String driverClassName = "com.mysql.jdbc.Driver"; private Integer initialSize = 2; private Integer minIdle = 1; private Integer maxActive = 20; private Integer maxWait = 60000; private Integer timeBetweenEvictionRunsMillis = 60000; private Integer minEvictableIdleTimeMillis = 300000; private String validationQuery = "SELECT 'x'"; private Boolean testWhileIdle = true; private Boolean testOnBorrow = false; private Boolean testOnReturn = false; private Boolean poolPreparedStatements = true; private Integer maxPoolPreparedStatementPerConnectionSize = 20; private String filters = "stat"; public void config(DruidDataSource dataSource) { dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); dataSource.setInitialSize(initialSize); //定义初始连接数 dataSource.setMinIdle(minIdle); //最小空闲 dataSource.setMaxActive(maxActive); //定义最大连接数 dataSource.setMaxWait(maxWait); //最长等待时间 // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 配置一个连接在池中最小生存的时间,单位是毫秒 dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); dataSource.setValidationQuery(validationQuery); dataSource.setTestWhileIdle(testWhileIdle); dataSource.setTestOnBorrow(testOnBorrow); dataSource.setTestOnReturn(testOnReturn); // 打开PSCache,并且指定每个连接上PSCache的大小 dataSource.setPoolPreparedStatements(poolPreparedStatements); dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); try { dataSource.setFilters(filters); } catch (SQLException e) { e.printStackTrace(); } } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public Integer getInitialSize() { return initialSize; } public void setInitialSize(Integer initialSize) { this.initialSize = initialSize; } public Integer getMinIdle() { return minIdle; } public void setMinIdle(Integer minIdle) { this.minIdle = minIdle; } public Integer getMaxActive() { return maxActive; } public void setMaxActive(Integer maxActive) { this.maxActive = maxActive; } public Integer getMaxWait() { return maxWait; } public void setMaxWait(Integer maxWait) { this.maxWait = maxWait; } public Integer getTimeBetweenEvictionRunsMillis() { return timeBetweenEvictionRunsMillis; } public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; } public Integer getMinEvictableIdleTimeMillis() { return minEvictableIdleTimeMillis; } public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) { this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; } public String getValidationQuery() { return validationQuery; } public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; } public Boolean getTestWhileIdle() { return testWhileIdle; } public void setTestWhileIdle(Boolean testWhileIdle) { this.testWhileIdle = testWhileIdle; } public Boolean getTestOnBorrow() { return testOnBorrow; } public void setTestOnBorrow(Boolean testOnBorrow) { this.testOnBorrow = testOnBorrow; } public Boolean getTestOnReturn() { return testOnReturn; } public void setTestOnReturn(Boolean testOnReturn) { this.testOnReturn = testOnReturn; } public Boolean getPoolPreparedStatements() { return poolPreparedStatements; } public void setPoolPreparedStatements(Boolean poolPreparedStatements) { this.poolPreparedStatements = poolPreparedStatements; } public Integer getMaxPoolPreparedStatementPerConnectionSize() { return maxPoolPreparedStatementPerConnectionSize; } public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) { this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize; } public String getFilters() { return filters; } public void setFilters(String filters) { this.filters = filters; } @Override public String toString() { return "DruidProperties{" + "url='" + url + '\'' + ", username='" + username + '\'' + ", password='" + password + '\'' + ", driverClassName='" + driverClassName + '\'' + ", initialSize=" + initialSize + ", minIdle=" + minIdle + ", maxActive=" + maxActive + ", maxWait=" + maxWait + ", timeBetweenEvictionRunsMillis=" + timeBetweenEvictionRunsMillis + ", minEvictableIdleTimeMillis=" + minEvictableIdleTimeMillis + ", validationQuery='" + validationQuery + '\'' + ", testWhileIdle=" + testWhileIdle + ", testOnBorrow=" + testOnBorrow + ", testOnReturn=" + testOnReturn + ", poolPreparedStatements=" + poolPreparedStatements + ", maxPoolPreparedStatementPerConnectionSize=" + maxPoolPreparedStatementPerConnectionSize + ", filters='" + filters + '\'' + '}'; } } ``` > 负责接收druid的配置,由于这里使用了多数据源,所以druid的配置需要我们自己配置,不能使用druid提供的spring boot starter 相应的配置文件如下 ```yaml spring: datasource: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://127.0.0.1:3306/multi_datasource1?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=UTC username: root password: 123456 #多数据源情况的配置 multi-datasource: url: jdbc:mysql://127.0.0.1:3306/multi_datasource2?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=UTC username: root password: 123456 dataSourceNames: - dataSource1 - dataSource2 ``` >- spring.datasource为前缀的为默认数据源 >- dataSourceNames数组中的参数可以自定义,不会影响数据源 ### 编写数据源上下文 ```java /** * datasource的上下文 * * @author earthchen * @date 2018/8/26 **/ public class DataSourceContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); /** * 设置数据源类型 * * @param dataSourceType 数据库类型 */ public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } /** * 获取数据源类型 */ public static String getDataSourceType() { return contextHolder.get(); } /** * 清除数据源类型 */ public static void clearDataSourceType() { contextHolder.remove(); } } ``` > 使用ThreadLocal保存数据源上下文,并进行切换 ### 继承AbstractRoutingDataSource,实现切换逻辑 ```java import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @author earthchen * @date 2018/8/26 **/ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } } ``` > 最核心的一个类 ## 测试 编写一个controller进行一个测试 ```java @GetMapping("/test") @TargetDataSource(name = "dataSource1") public String test() { User user = new User(); user.setUsername("test1"); user.setPassword("test1"); userService.insert(user); return "ok"; } @GetMapping("/test2") @TargetDataSource(name = "dataSource2") public String test2() { User user = new User(); user.setUsername("test1"); user.setPassword("test1"); userService.insert(user); return "ok"; } ``` 分别访问两个controller,观察插入结果
数据库数据源配置
说明:这个类中包含了许多默认配置,若这些配置符合您的情况, * 您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可