spring-boot使用aop进行多数据源切换
创建一个spring boot项目,并引入druid mysql aop等相关依赖
<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>
import java.lang.annotation.*;
/**
* 多数据源标识
*
* @author earthchen
* @date 2018/8/26
**/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface TargetDataSource {
String name() default "";
}
使用aop切换数据源的规则就是被该注解标识的方法
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的配置类和一个多数据源的配置类,我们需要在yml里编写合适的配置,也需要创建合适的类接受自定义配置
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) +
'}';
}
}
负责接收多数据源配置
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
相应的配置文件如下
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
/**
* 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保存数据源上下文,并进行切换
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进行一个测试
@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,观察插入结果