携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
日常工作中我们在一个springboot项目中,经常会有不同数据源的场景,那就需要配置动态数据源 dynamic-datasource,如果我们项目中用的是mybatis-plus,那就很方便去配置,如果用的是mybatis需要手动配置不同的数据源,指定不能的mapper扫描包,也可以自己实现一个动态切换数据源(spring已经给我们提供了一个接口)
一、springboot+mybatis
1、引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
2、配置yml文件
spring:
datasource:
druid:
mysqlBeyond
type: com.alibaba.druid.pool.xa.DruidXADataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql:${MYSQL_HOST}:${MYSQL_PORT}/${DATABASE_NAME}?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: ${MYSQL_USER:root}
password: ${MYSQL_PASS:root}
initialSize: ${global.druid.initial-size}
minIdle: ${global.druid.minIdle}
maxActive: ${global.druid.maxActive}
# 配置获取连接等待超时的时间
maxWait: ${global.druid.maxWait}
mysql:
type: com.alibaba.druid.pool.xa.DruidXADataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql:${SLAVE1_MYSQL_HOST}:${SLAVE1_MYSQL_PORT}/${SLAVE1_DATABASE_NAME}?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: ${SLAVE1_MYSQL_USER:root}
password: ${SLAVE1_MYSQL_PASS:root}
initialSize: ${global.druid.initial-size}
minIdle: ${global.druid.minIdle}
maxActive: ${global.druid.maxActive}
maxWait: ${global.druid.maxWait}
3、分开配置数据源
mysqlBeyond配置类MySQLBeyondDataSourceConfig:
@Configuration
@MapperScan(basePackages = MySQLBeyondDataSourceConfig.BASEPACKAGES, sqlSessionFactoryRef = "mysqlBeyondSqlSessionFactory")
public class MySQLBeyondDataSourceConfig {
public static final String BASEPACKAGES = "com.test.job.**.mysqlbeyond.mapper";
private static final String MYSQLMAPPER = "classpath:mybatis/mapper/mysqlbeyond/*.xml";
@Bean(name = "mysqlBeyond")
@ConfigurationProperties(prefix = "spring.datasource.druid.mysqlbeyond")
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "mysqlBeyondTransactionManager")
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean(name = "mysqlBeyondSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("mysqlBeyond") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MYSQLMAPPER));
return sessionFactoryBean.getObject();
}
}
mysql配置类MySQLDataSourceConfig:
@Configuration
@MapperScan(basePackages = MySQLDataSourceConfig.BASEPACKAGES, sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MySQLDataSourceConfig {
public static final String BASEPACKAGES = "com.onewo.job.executor.**.mysql.mapper";
private static final String MYSQLMAPPER = "classpath:mybatis/mapper/mysql/*.xml";
@Bean(name = "mysql")
@ConfigurationProperties("spring.datasource.druid.mysql")
@Primary
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "mysqlTransactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("mysql") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MYSQLMAPPER));
return sessionFactoryBean.getObject();
}
}
二、springboot+mybati-plus
1、引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
2、一般使用情况下使用@DS("数据源名称"),我们进行切换数据源,只需要在配置文件中配置一组DataSource就可以了。
@DS("slave")
public List<User> list(String area) {
List<User> list = slaveMapper.list(area);
return list;
}
@DS("master")
public List<String> list(String code) {
List<String> list = masterMapper.list(code);
return list;
}
配置application.yml
dynamic:
primary: master
datasource:
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/master
username: root
password: 123456
slave:
url: jdbc:mysql://127.0.0.1:3306/slave
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
是不是使用起来很方便!!!
3、特殊情况@DS("")不能满足我们的所有需求,需要手动切换;
比如我们在slave数据源的service方法中要使用master中的数据,数据在slave,字典在master中,于是如直接注解(这样写是错误的,并不能达到我们想要的效果)
@DS("slave")
public List<KeyValueVo> list(String areaCode) {
List<KeyValueVo> list = slaveMapper.list(areaCode);
List<Dict> educationes = listDict("education");
...后续操作...
}
@DS("master")
public List<Dict> listDict(String dictType) {
List<Dict> list = dictMapper.list(dictType);
return list;
}
原因: mybatis-plus数据源是利用spring-aop实现的,对于aop而言它是以每次请求为单位的,简单的说,虽然我们使用了两个方法,分别配置了两个@DS("),但其实第二个并不会生效。
解决办法,对于一次请求两个数据源在把第二个数据源改为手动修改,下面的手动修改配置:
public List<Dict> listDict(String dictType) {
DynamicDataSourceContextHolder.push("master");
List<Dict> list = dictMapper.list(dictType);
DynamicDataSourceContextHolder.poll();
return list;
}
private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new ThreadLocal() {
protected Object initialValue() {
return new ArrayDeque();
}
};
private DynamicDataSourceContextHolder() {
}
public static String peek() {
return (String)((Deque)LOOKUP_KEY_HOLDER.get()).peek();
}
public static void push(String ds) {
((Deque)LOOKUP_KEY_HOLDER.get()).push(StringUtils.isEmpty(ds) ? "" : ds);
}
public static void poll() {
Deque<String> deque = (Deque)LOOKUP_KEY_HOLDER.get();
deque.poll();
if (deque.isEmpty()) {
LOOKUP_KEY_HOLDER.remove();
}
}
在DynamicDataSourceContextHolder中定义了一个双端队列LOOKUP_KEY_HOLDER,它的peek()方法每次取得时当前线程的首个,所以我们在手动却换的过程中,加入一个我们想要的datasource,然后通过**poll()**方法删除就能达到我们手动切换的目的。