前置: 这里说明一下动态数据源与多数据源,多数据源一般是配置多个数据源,然后然后通过mybatis配置把不同库的对应mapper放在不同的包,然后不同的库的mapper对应不同的DataSource。动态切换数据源一般是针对我们的需要将DataSource切换到对应的数据库。前者是写死的同一个mapper或者同一个function固定对应一个库,后者可以根据业务需求将同一个mapper和function都可以操作不同的库。例如多数据源可以在一个项目中实现多数据库,多业务,读写分离(sql层面,固定的查从库,写主库),相比多数据源,动态数据源同样可以实现,同时动态数据源可以更好的实现读写分离,可以做到同一个查询方法既可以做到查从库,同时在处理其他业务是查主库(具体的开始看业务需求) ##有不同看法的欢迎品论留言
1.开发环境
eclipse+jdk1.8+maven+mysql
2.pom配置 这里主要列出了数据库连接以及AOP的包
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.application.yml 配置文件
spring:
datasource: # 多数据源
# type: com.alibaba.druid.pool.DruidDataSource
# 主库
master:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置初始值
initial-size: 1
min-idle: 1
max-active: 20
# 获取连接等待超时时间
max-wait: 6000
# 监控关闭空闲连接时间间隔
time-between-eviction-runs-millis: 60000
# 每个连接池最小的生命周期
min-evictable-idle-time-millis: 360000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
remove-abandoned: true
remove-abandoned-timeout: 1800
# 从库
slave:
type: com.alibaba.druid.pool.DruidDataSource
# 注意不是jdbcUrl, Durid是url
url: jdbc:mysql://localhost:3306/testSlave?useSSL=false&useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置初始值
initial-size: 1
min-idle: 1
max-active: 20
# 获取连接等待超时时间
max-wait: 6000
# 监控关闭空闲连接时间间隔
time-between-eviction-runs-millis: 60000
# 每个连接池最小的生命周期
min-evictable-idle-time-millis: 360000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
remove-abandoned: true
remove-abandoned-timeout: 1800
mybatis:
mapper-locations: classpath*:mapper/*Mapper.xml
type-aliases-package: com.***.entity #按自己包名来
check-config-location: true
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.继承AbstractRoutingDataSource 抽象类,实现动态获取数据源
public class DataSourceRouter extends AbstractRoutingDataSource {
private static String dataSourceconfig = "master"; //默认主库
@Override
protected Object determineCurrentLookupKey() {
// log.info(" 当前数据源: " + DataSourceContextHolder.getCurrentDataSource());
// return DataSourceContextHolder.getCurrentDataSource();
return dataSourceconfig;
}
public static void setMater() {
dataSourceconfig = "master";
System.out.println("设置为主库");
}
public static void setSlave() {
dataSourceconfig = "slave";
System.out.println("设置为从库");
}
}
@Configuration
public class DataSourceConfig {
/***
* 注意这里用的 Druid 连接池
*/
@Bean(name = "dbMaster")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource dbMaster() {
// log.info("master数据源");
return new DruidDataSource();
}
@Bean(name = "dbSlave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource dbSlave() {
// log.info("slave数据源");
return new DruidDataSource();
}
/***
* @Primary: 相同的bean中,优先使用用@Primary注解的bean.
* @Qualifier:: 这个注解则指定某个bean有没有资格进行注入。
*/
@Primary
@Bean(name = "dataSourceRouter") // 对应Bean: DataSourceRouter
public DataSource dataSourceRouter(@Qualifier("dbMaster") DataSource master, @Qualifier("dbSlave") DataSource slave) {
DataSourceRouter dataSourceRouter = new DataSourceRouter();
//配置多数据源
Map<Object, Object> map = new HashMap<>(5);
map.put("master", master); // key需要跟ThreadLocal中的值对应
map.put("slave", slave);
// master 作为默认数据源
dataSourceRouter.setDefaultTargetDataSource(master);
dataSourceRouter.setTargetDataSources(map);
return dataSourceRouter;
}
// 注入动态数据源 DataSourceTransactionManager 用于事务管理(事务回滚只针对同一个数据源)
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("dataSourceRouter") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
5.使用AOP实现动态切换数据源
@Aspect
@Order(1) // 数据源的切换要在数据库事务之前, 设置AOP执行顺序(需要在事务之前,否则事务只发生在默认库中, 数值越小等级越高)
@Component
public class DataSourceAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
// 切点, 注意这里是在service层(具体是配置dao层还是service层需要看自己业务,原则就是当前业务需要在主库执行还是从库执行的判断依据在哪)
@Pointcut("execution(* com.***.service.impl..*.*(..)))")
public void aspect() {
}
@Before("aspect()")
private void before(JoinPoint point) {
//进入切面
String method = point.getSignature().getName();//当前切入的方法名
// point.getSignature().getDeclaringType().getName();//当前切入的class
// if (method.startsWith("query") || method.startsWith("select") || method.startsWith("get")
// || method.startsWith("find") || method.startsWith("read")) { //根据自己业务做判断主库从库切换
DataSourceRouter.setMater();//设置为当前使用主库
// DataSourceRouter.setSlave();//设置为当前使用从库
}
}
// ----------
// 切面结束, 重置线程变量 ,
@After("aspect()")
public void after(JoinPoint joinPoint) {
//切面结束
}
}
最后启动类上面需要加上
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
需要注意的是当service里面的方法存在事务时@Transactional ,会先获取数据源,后进入切面,开始事务后无法在切换数据源