spring-boot使用aop进行多数据源切换
多数据源
环境搭建
创建一个spring boot项目,并引入druid mysql aop等相关依赖
代码语言:javascript复制<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>${mybatisplus-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
编写数据源注解
代码语言:javascript复制import java.lang.annotation.*;
/**
* 多数据源标识
*
* @author earthchen
* @date 2018/8/26
**/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TargetDataSource {
String name() default "";
}
使用aop切换数据源的规则就是被该注解标识的方法
编写多数据源配置文件
代码语言:javascript复制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<Object, Object> 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里编写合适的配置,也需要创建合适的类接受自定义配置
代码语言:javascript复制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)
'}';
}
}
代码语言:javascript复制负责接收多数据源配置
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
/**
* <p>数据库数据源配置</p>
* <p>说明:这个类中包含了许多默认配置,若这些配置符合您的情况,
* 您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可</p>
*
* @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
相应的配置文件如下
代码语言:javascript复制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数组中的参数可以自定义,不会影响数据源
编写数据源上下文
代码语言:javascript复制/**
* datasource的上下文
*
* @author earthchen
* @date 2018/8/26
**/
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 设置数据源类型
*
* @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,实现切换逻辑
代码语言:javascript复制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进行一个测试
代码语言:javascript复制@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,观察插入结果
项目代码地址:https://gitee.com/earthchen/multi-datasource-demo