定义:在项目中针对一个数据库都为其建立一套独立的数据处理逻辑,包括数据源(DataSource),会话工厂(SqlSessionFactory),连接,DAO操作。
项目工程结构如下所示,主要从实体类和Mapper层开始进行划分,实际工作可从Service层进行划分。
├─config ---------------------------------- // 数据源配置
├─controller ------------------------------ // web服务
├─entity ---------------------------------- // 实体类
│ ├─master
│ └─slave
├─mapper ---------------------------------- // dao操作类
│ ├─master
│ └─slave
└─vo -------------------------------------- // 视图返回对象
springboot默认的配置文件是application.properties
。我们将数据库连接信息单独写一个配置文件jbdc.properties
,放到Resource目录下,文件内容如下:
# master
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/mytest?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.master.username=root
spring.datasource.master.password=111111
# slave
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/my_test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.slave.username=root
spring.datasource.slave.password=111111
新增数据源配置类DatasourceConfig,读取配置文件信息,创建数据源Bean对象交给Spring容器管理。
@Configuration
@PropertySource("classpath:jdbc.properties")
public class DatasourceConfig {
@Bean("master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("slave")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource(){
return DataSourceBuilder.create().build();
}
}
分别为Master和Slave数据库添加配置类,用于创建SqlSessionFactory对象,交给Spring容器。
@Configuration
@MapperScan(basePackages = "me.mason.demo.basicmultidatasource.mapper.master", sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterMybatisConfig {
/**
* 注意,此处需要使用MybatisSqlSessionFactoryBean,不是SqlSessionFactoryBean,
* 否则,使用mybatis-plus的内置函数时就会报invalid bound statement (not found)异常
*/
@Bean("masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("master") DataSource dataSource) throws Exception {
// 设置数据源
MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
//mapper的xml文件位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
String locationPattern = "classpath*:/mapper/master/*.xml";
mybatisSqlSessionFactoryBean.setMapperLocations(resolver.getResources(locationPattern));
//对应数据库的entity位置
String typeAliasesPackage = "me.mason.demo.basicmultidatasource.entity.master";
mybatisSqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
return mybatisSqlSessionFactoryBean.getObject();
}
}
@Configuration
@MapperScan(basePackages = "me.mason.demo.basicmultidatasource.mapper.slave", sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveMybatisConfig {
/**
* 注意,此处需要使用MybatisSqlSessionFactoryBean,不是SqlSessionFactoryBean,
* 否则,使用mybatis-plus的内置函数时就会报invalid bound statement (not found)异常
*/
@Bean("slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slave") DataSource dataSource) throws Exception {
// 设置数据源
MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
mybatisSqlSessionFactoryBean.setDataSource(dataSource);
//mapper的xml文件位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
String locationPattern = "classpath*:/mapper/slave/*.xml";
mybatisSqlSessionFactoryBean.setMapperLocations(resolver.getResources(locationPattern));
//对应数据库的entity位置
String typeAliasesPackage = "me.mason.demo.basicmultidatasource.entity.slave";
mybatisSqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
return mybatisSqlSessionFactoryBean.getObject();
}
}
由于项目使用了MyBatis Plus,所以我们创建的会话工厂Bean对象是MybatisSqlSessionFactoryBean。
实体:
@Data
@TableName("test_user")
public class MasterTestUser implements Serializable {
private static final long serialVersionUID = 1L;
/** id */
private Long id;
/** 姓名 */
private String name;
...
}
Mapper:
@Repository
public interface MasterTestUserMapper extends BaseMapper<MasterTestUser> {
/**
* 自定义查询
* @param wrapper 条件构造器
*/
List<MasterTestUser> selectAll(@Param(Constants.WRAPPER)Wrapper<MasterTestUser> wrapper);
}
slave数据库对应的Mapper同上类似。
xml文件:
<mapper namespace="me.mason.demo.basicmultidatasource.mapper.master.MasterTestUserMapper">
<select id="selectAll" resultType="masterTestUser">
select * from test_user
<if test="ew!=null">
${ew.customSqlSegment}
if>
select>
mapper>
@RestController
@RequestMapping("/user")
public class TestUserController {
@Autowired
private MasterTestUserMapper masterTestUserMapper;
@Autowired
private SlaveTestUserMapper slaveTestUserMapper;
/**
* 查询全部
*/
@GetMapping("/listall")
public Object listAll() {
//master库,自定义接口查询
QueryWrapper<MasterTestUser> queryWrapper = new QueryWrapper<>();
List<MasterTestUser> resultData = masterTestUserMapper.selectAll(queryWrapper.isNotNull("name"));
//slave库,mp内置接口
List<SlaveTestUser> resultDataSlave = slaveTestUserMapper.selectList(null);
//返回
Map<String, Object> result = new HashMap<>();
result.put("master" , resultData);
result.put("slave" , resultDataSlave);
return ResponseResult.success(result);
}
}
**总结:**多套数据源符合开闭原则,当我们新增或者删除数据库时,只要修改对应的即可,不会影响到原有数据库。在使用时,我们需要注入对应的mapper,再调用其对应的方法,对于很多方法,代码实现逻辑是一致的,只是选择的数据源不同,这就造成了代码存在一定的冗余,缺乏灵活性。另外,对于一主多从的情况,若需要对多个从库进行负载均衡,相对比较麻烦。
定义:确定数量的多个数据源共用一个会话工厂,根据条件动态选取数据源进行连接、SQL 操作。
相比于多套数据源项目,动态数据源不再是为每个数据库建立一套独立的数据处理逻辑,而是根据实际业务需求来统一逻辑,再需要切换数据源的实际进行处理即可,所以我们不需要在Service层或者Mapper层就对数据库进行划分。
SpringBoot动态数据源的本质是将多个DataSource存储在一个Map集合中,当需要用到某个数据源时,从Map中获取此数据源进行处理。Spring提供了抽象类AbstractRoutingDataSource
,实现了此功能,所以我们实现动态数据源时继承它,实现自己的获取数据源的逻辑即可。
├─annotation ---- // 自定义注解
├─aop ----------- // 切面
├─config -------- // 数据源配置
├─constants ----- // 常用注解
├─context ------- // 自定义上下文
├─controller ---- // 访问接口
├─entity -------- // 实体
├─mapper -------- // 数据库dao操作
├─service ------- // 服务类
└─vo ------------ // 视图返回数据
二、添加数据库配置文件
jdbc.properties :
# master
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/mytest?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.master.username=root
spring.datasource.master.password=111111
# slave
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/my_test1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8
spring.datasource.slave.username=root
spring.datasource.slave.password=111111
DynamicDataSourceConfig.class :
@Configuration
@PropertySource("classpath:jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
我们不需要为每个数据库创建SqlSessionFactory对象,直接使用SpringBoot自动配置的SqlSessionFactory 即可。
添加jdbc起步依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
添加动态数据源类:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 此处暂时返回固定 master 数据源, 后面按动态策略修改
return DataSourceConstants.DS_KEY_MASTER;
}
}
在DynamicDataSourceConfig.class
设置动态数据源:
使用集合保存多个DataSource,并设置到动态数据源对象中,且设置动态数据源的优先级最高。
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
//设置动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
在 DynamicDataSourceConfig.class
中,排除 DataSourceAutoConfiguration
的自动配置:
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
完整的DynamicDataSourceConfig.class
配置如下:
@Configuration
@PropertySource("classpath:jdbc.properties")
@MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper")
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.DS_KEY_MASTER)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.DS_KEY_SLAVE)
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
//设置动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
}
数据源key的上下文:
前面AbstractRoutingDataSource
的实现类DynamicDataSource
,固定写了一个数据源路由策略,总是返回 master,显然不是我们想要的。我们想要的是在需要的地方,想切换就切换。因此,需要有一个动态获取数据源 key 的地方(我们称为上下文),对于 web 应用,访问以线程为单位,使用 ThreadLocal 就比较合适
public class DynamicDataSourceContextHolder {
/**
* 动态数据源名称上下文
*/
private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
/**
* 设置/切换数据源
*/
public static void setContextKey(String key){
DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
}
/**
* 获取数据源名称
*/
public static String getContextKey(){
String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
return key == null?DataSourceConstants.DS_KEY_MASTER:key;
}
/**
* 删除当前数据源名称
*/
public static void removeContextKey(){
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
设置DynamicDataSource
路由策略:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
基本使用:
@RestController
@RequestMapping("/user")
public class TestUserController {
@Autowired
private TestUserMapper testUserMapper;
/**
* 查询全部
*/
@GetMapping("/listall")
public Object listAll() {
int initSize = 2;
Map<String, Object> result = new HashMap<>(initSize);
//默认master查询
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
List<TestUser> resultData = testUserMapper.selectAll(queryWrapper.isNotNull("name"));
result.put(DataSourceConstants.DS_KEY_MASTER, resultData);
//切换数据源,在slave查询
DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_SLAVE);
List<TestUser> resultDataSlave = testUserMapper.selectList(null);
result.put(DataSourceConstants.DS_KEY_SLAVE, resultDataSlave);
//恢复数据源
DynamicDataSourceContextHolder.removeContextKey();
//返回数据
return ResponseResult.success(result);
}
}
上面我们通过setContextKey
和 removeContextKey
进行数据源的切换,每次切换都需要我们重复手动调用方法,造成了代码冗余,下面我们通过配置切面和自定义注解,实现添加注解实现切换。
添加自定义注解DS
:
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
/**
* 数据源名称
*/
String value() default DataSourceConstants.DS_KEY_MASTER;
}
添加aop起步依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
定义切面:
annotation
指定注解DS
的值进行数据源切换,处理完后,恢复数据源。@Aspect
@Component
public class DynamicDataSourceAspect {
@Pointcut("@annotation(me.mason.demo.dynamicdatasource.annotation.DS)")
public void dataSourcePointCut(){
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsKey = getDSAnnotation(joinPoint).value();
DynamicDataSourceContextHolder.setContextKey(dsKey);
try{
return joinPoint.proceed();
}finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
/**
* 根据类或方法获取数据源注解
*/
private DS getDSAnnotation(ProceedingJoinPoint joinPoint){
Class<?> targetClass = joinPoint.getTarget().getClass();
DS dsAnnotation = targetClass.getAnnotation(DS.class);
// 先判断类的注解,再判断方法注解
if(Objects.nonNull(dsAnnotation)){
return dsAnnotation;
}else{
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DS.class);
}
}
}
基本使用:
创建TestUserService
类,定义两个方法,分别是从 master 和 slave 中获取数据,使用了注解DS
/**
* 查询master库User
*/
@DS(DataSourceConstants.DS_KEY_MASTER)
public List<TestUser> getMasterUser() {
QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
}
/**
* 查询slave库User
*/
@DS(DataSourceConstants.DS_KEY_SLAVE)
public List<TestUser> getSlaveUser() {
return testUserMapper.selectList(null);
}
在controller层调用不同的方法:
@GetMapping("/listall")
public Object listAll() {
int initSize = 2;
Map<String, Object> result = new HashMap<>(initSize);
//默认master数据源查询
List<TestUser> masterUser = testUserService.getMasterUser();
result.put(DataSourceConstants.DS_KEY_MASTER, masterUser);
//从slave数据源查询
List<TestUser> slaveUser = testUserService.getSlaveUser();
result.put(DataSourceConstants.DS_KEY_SLAVE, slaveUser);
//返回数据
return ResponseResult.success(result);
}
引入依赖:
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId>
<version>${version}version>
dependency>
配置数据源:
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx) # 内置加密,使用请查看详细文档
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
使用@DS切换数据源:
@DS可注解在方法或者类上,同时存在就近原则,方法上注解优先于类上注解。
注意:
DruidDataSourceAutoConfigure
会注入一个DataSourceWrapper
,其会在原生的spring.datasource
下找url,username,password
等。而我们动态数据源的配置路径是变化的,所以需要排除:
spring:
autoconfigure:
exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure
或者在启动类上排除:
@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
**总结:**上面手动编码自定义注解和配置切面实现了数据源的切换,MyBatis-Plus也提供了相应的插件,基本实现原理都差不多,实际开发中,直接引入插件起步依赖,并配置数据源即可。但是不管是我们手动编码实现还是集成MyBatis-Plus插件,都仅仅只是实现了数据源的切换,没有涉及事务相关的处理,当我们定义的方法中嵌套使用不同数据源进行操作时,spring提供的事务是无效的,虽然我们在开发中应该尽量避免跨库事务,但如果避免不了,则需要使用分布式事务。
不管是多套数据源还是动态数据源,数据源本身在编码阶段就已经确定,无法实时添加数据源并进行切换。
定义:根据参数添加数据源,并进行数据源切换,数据源数量不确定。通常用于对多个数据库的管理工作。
思路:SpringBoot动态数据源的本质是将多个DataSource存储在一个Map集合中,当需要用到某个DataSource时,从Map中获取此数据源进行处理。Spring提供了抽象类AbstractRoutingDataSource
,实现了此功能。我们可以把新的数据源添加到SpringBoot保存数据源的集合中,以供我们切换使用。但是从AbstractRoutingDataSource
源码可看出,存放数据源的 Map targetDataSources
是 private 的,而且并没有提供对此 Map 本身的操作,它提供的是两个关键操作:setTargetDataSources
及 afterPropertiesSet
。其中 setTargetDataSources
设置整个 Map 目标数据源,afterPropertiesSet
则是对 Map 目标数据源进行解析,形成最终使用的 resolvedDataSources
,可见以下源码:
因此,为实现动态添加数据源到 Map 的功能,我们可以根据这两个关键操作进行处理。
添加动态数据源:
backupTargetDataSources
作为原 targetDataSources
的拷贝setTargetDataSources
及 afterPropertiesSet
完成新数据源添加。afterPropertiesSet
的作用很重要,它负责解析成可用的目标数据源。public class DynamicDataSource extends AbstractRoutingDataSource {
private Map<Object, Object> backupTargetDataSources;
/**
* 自定义构造函数
*/
public DynamicDataSource(DataSource defaultDataSource,Map<Object, Object> targetDataSource){
backupTargetDataSources = targetDataSource;
super.setDefaultTargetDataSource(defaultDataSource);
super.setTargetDataSources(backupTargetDataSources);
super.afterPropertiesSet();
}
/**
* 添加新数据源
*/
public void addDataSource(String key, DataSource dataSource){
this.backupTargetDataSources.put(key,dataSource);
super.setTargetDataSources(this.backupTargetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
动态数据源配置:
@Bean
@Primary
public DataSource dynamicDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource());
// 有参构造函数
return new DynamicDataSource(masterDataSource(), dataSourceMap);
}
添加Spring上下文工具类:
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextHolder.applicationContext = applicationContext;
}
/**
* 返回上下文
*/
public static ApplicationContext getContext(){
return SpringContextHolder.applicationContext;
}
}
添加数据源操作工具类:
public class DataSourceUtil {
/**
* 创建新的数据源,注意:此处只针对 MySQL 数据库
*/
public static DataSource makeNewDataSource(DbInfo dbInfo){
String url = "jdbc:mysql://"+dbInfo.getIp() + ":"+dbInfo.getPort()+"/"+dbInfo.getDbName()
+"?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8";
String driveClassName = StringUtils.isEmpty(dbInfo.getDriveClassName())? "com.mysql.cj.jdbc.Driver":dbInfo.getDriveClassName();
return DataSourceBuilder.create().url(url)
.driverClassName(driveClassName)
.username(dbInfo.getUsername())
.password(dbInfo.getPassword())
.build();
}
/**
* 添加数据源到动态源中
*/
public static void addDataSourceToDynamic(String key, DataSource dataSource){
DynamicDataSource dynamicDataSource = SpringContextHolder.getContext().getBean(DynamicDataSource.class);
dynamicDataSource.addDataSource(key,dataSource);
}
/**
* 根据数据库连接信息添加数据源到动态源中
* @param key
* @param dbInfo
*/
public static void addDataSourceToDynamic(String key, DbInfo dbInfo){
DataSource dataSource = makeNewDataSource(dbInfo);
addDataSourceToDynamic(key,dataSource);
}
}
添加Mapper:
@Repository
public interface TableMapper extends BaseMapper<TestUser> {
/**
* 查询表信息
*/
@Select("select table_name, table_comment, create_time, update_time " +
" from information_schema.tables " +
" where table_schema = (select database())")
List<Map<String,Object>> selectTableList();
}
定义数据库连接信息对象:
@Data
public class DbInfo {
private String ip;
private String port;
private String dbName;
private String driveClassName;
private String username;
private String password;
}
添加数据源并使用:
/**
* 根据数据库连接信息获取表信息
*/
@GetMapping("table")
public Object findWithDbInfo(DbInfo dbInfo) throws Exception {
//数据源key
String newDsKey = System.currentTimeMillis()+"";
//添加数据源
DataSourceUtil.addDataSourceToDynamic(newDsKey,dbInfo);
DynamicDataSourceContextHolder.setContextKey(newDsKey);
//查询表信息
List<Map<String, Object>> tables = tableMapper.selectTableList();
DynamicDataSourceContextHolder.removeContextKey();
return ResponseResult.success(tables);
}
上面实现参数化变更数据源的功能,但是仍需要手动的调用setContextKey
和removeContextKey
方法进行数据源切换变更,下面采用JDK动态代理的方法,取消模板代码。
添加动态代理类:
public class JdkParamDsMethodProxy implements InvocationHandler {
// 代理对象及相应参数
private String dataSourceKey;
private DbInfo dbInfo;
private Object targetObject;
public JdkParamDsMethodProxy(Object targetObject, String dataSourceKey, DbInfo dbInfo) {
this.targetObject = targetObject;
this.dataSourceKey = dataSourceKey;
this.dbInfo = dbInfo;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//切换数据源
DataSourceUtil.addDataSourceToDynamic(dataSourceKey, dbInfo);
DynamicDataSourceContextHolder.setContextKey(dataSourceKey);
//调用方法
Object result = method.invoke(targetObject, args);
DynamicDataSourceContextHolder.removeContextKey();
return result;
}
/**
* 创建代理
*/
public static Object createProxyInstance(Object targetObject, String dataSourceKey, DbInfo dbInfo) throws Exception {
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader()
, targetObject.getClass().getInterfaces(), new JdkParamDsMethodProxy(targetObject, dataSourceKey, dbInfo));
}
}
使用:
@GetMapping("table")
public Object findWithDbInfo(DbInfo dbInfo) throws Exception {
//数据源key
String newDsKey = System.currentTimeMillis()+"";
//使用代理切换数据源
TableMapper tableMapperProxy = (TableMapper)JdkParamDsMethodProxy.createProxyInstance(tableMapper, newDsKey, dbInfo);
List<Map<String, Object>> tables = tableMapperProxy.selectTableList();
return ResponseResult.success(tables);
}