前提项目架构:SpringBoot+mybatis-plus-Druid
方式一、基于dynamic-datasource-spring-boot-starter实现(配置简单)
1、添加依赖:
<!--引入多数据源需要的依赖包-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
2、在application.yml中配置多数据源
spring:
application:
name: zhangjiadayuan-server
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# Druid的其他属性配置
druid:
# 初始化时建立物理连接的个数
initial-size: 10
# 连接池的最小空闲数量
min-idle: 5
# 连接池最大连接数量
max-active: 20
# 获取连接时最大等待时间,单位毫秒
max-wait: 60000
# 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
test-while-idle: true
# 既作为检测的间隔时间又作为testWhileIdel执行的依据
time-between-eviction-runs-millis: 60000
# 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接(配置连接在池中的最小生存时间)
min-evictable-idle-time-millis: 30000
# 用来检测数据库连接是否有效的sql 必须是一个查询语句(oracle中为 select 1 from dual)
validation-query: SELECT 1 FROM DUAL
# 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-borrow: false
# 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
test-on-return: false
# 是否缓存preparedStatement, 也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。
pool-prepared-statements: false
# 置监控统计拦截的filters,去掉后监控界面sql无法统计,stat: 监控统计、Slf4j:日志记录、waLL: 防御sqL注入
filters: stat,wall,slf4j
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
max-pool-prepared-statement-per-connection-size: -1
# 合并多个DruidDataSource的监控数据
use-global-data-source-stat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
web-stat-filter:
# 是否启用StatFilter默认值true
enabled: true
# 添加过滤规则
url-pattern: /*
# 忽略过滤的格式
exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico
stat-view-servlet:
# 是否启用StatViewServlet默认值true
enabled: true
# 访问路径为/druid时,跳转到StatViewServlet
url-pattern: /druid/*
# 是否能够重置数据
reset-enable: false
# 需要账号密码才能访问控制台,默认为root http://localhost:8585/druid/
login-username: root
login-password: 123456
# IP白名单
allow: 127.0.0.1
# IP黑名单(共同存在时,deny优先于allow)
deny:
dynamic:
primary: master #设置默认的数据源或者数据源组
strict: false #严格匹配数据源,默认为false,true未匹配到数据源时会抛出异常,false使用默认数据源
datasource:
master:
# 数据源基本配置
username: root
password: root123
url: jdbc:mysql://127.0.0.1:3306/zhangjiadayuan?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
# driver-class需要注意mysql驱动的版本(com.mysql.cj.jdbc.Driver 或 com.mysql.jdbc.Driver)
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
# 数据源基本配置
username: root
password: root123
url: jdbc:mysql://127.0.0.1:3306/zhangjiadayuan2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
# driver-class需要注意mysql驱动的版本(com.mysql.cj.jdbc.Driver 或 com.mysql.jdbc.Driver)
driver-class-name: com.mysql.cj.jdbc.Driver
3、排除掉Druid的数据源自动配置类
在启动类中需要排除掉DruidDataSourceAutoConfigure.class,就是取消Druid的数据源的自动配置类。
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
@ComponentScan("cn.zhang.*")
@MapperScan("cn.zhang.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
4、切换数据源
使用@DS("数据源名字")切换数据源,@DS注解可以用在方法上或类上,同时存在就近原则,方法上注解优先于类上;没有使用@DS默认使用默认数据源。
方式二、基于自定义注解
1、配置文件配置多数据源
spring:
datasource:
druid:
db1:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
username: root
password: 123456
db2:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL&autoReconnect=true
username: root
password: 123456
test-on-borrow: true
2、定义数据源配置类
该类的作用就是初始化数据源DataSource实例,以及初始化SqlSessionFactory实例。这里需要注意的是必须使用MybatisSqlSessionFactoryBean来获取会话工厂SqlSessionFactory,不然的话,baseMapper中的生成动态SQL的方法就不能使用了。
@Configuration(basePackages = MyBatisConfig.BASE_PACKAGE, sqlSessionTemplateRef = "sqlSessionTemplate")
public class DataSourceConfigurer {
//mapper模式下的接口层
static final String BASE_PACKAGE = "cn.zhang.mapper";
public static final String CLASSPATH_MAPPING_XML = "classpath*:mapper/*.xml";
/**
* 配置数据源
*
* @return
*/
@Bean(name = "db1")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.db1")
public DataSource db1() {
return DruidDataSourceBuilder.create().build();
}
/**
* 配置数据源
*
* @return
*/
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.druid.db2")
public DataSource db2() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<Object, Object>(2);
dataSourceMap.put(DataSourceKey.Master.getName(), db1());
dataSourceMap.put(DataSourceKey.SLAVE.getName(), db2());
dynamicRoutingDataSource.setDefaultTargetDataSource(dcs());
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
//MybatisPlus使用的是MybatisSqlSessionFactory
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
//此处设置为了解决找不到mapper文件的问题
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(CLASSPATH_MAPPING_XML));
return sqlSessionFactoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
/**
* 事务
*
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
/**
* 该类继承自 AbstractRoutingDataSource 类,
* 在访问数据库时会调用该类的 determineCurrentLookupKey() 方法获取数据库实例的 key
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
logger.info("current datasource is : {}", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
public class DynamicDataSourceContextHolder {
private static ThreadLocal<Object> CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceKey.DCS.getName());
public static List<Object> dataSourceKeys = new ArrayList<Object>();
public static void setDataSourceKey(String key){
CONTEXT_HOLDER.set(key);
}
public static Object getDataSourceKey(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSourceKey(){
CONTEXT_HOLDER.remove();
}
public static Boolean containDataSourceKey(String key){
return dataSourceKeys.contains(key);
}
}
public enum DataSourceKey {
MASTER("db1"),
SLAVE("db2");
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private DataSourceKey(String name) {
this.name = name;
}
}
3、自定义注解和定义切面
//该注解只是作用在方法上,这里默认的数据源是DB1
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default "db1";
}
切面顾名思义就是拦击标注自定义注解@TargetDataSource注解的方法,并且根据注解指定的数据源的key切换数据源。
@Aspect
@Component
public class DynamicDataSourceAspect {
private Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(targetDataSource)")
public void switchDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDataSource.value().getName())) {
logger.error("DataSource [{}] doesn't exist, use default DataSource [{}]", targetDataSource.value());
} else {
DynamicDataSourceContextHolder.setDataSourceKey(targetDataSource.value().getName());
logger.info("Switch DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
}
}
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.info("Restore DataSource to [{}] in Method [{}]", DynamicDataSourceContextHolder.getDataSourceKey(), joinPoint.getSignature());
}
}
4、使用切换数据源
//也可用与Service方法或类上
@TargetDataSource("db2")
String getClassStudent(@Param("open_id") String openId);