多套数据源:
定义:在项目中针对一个数据库都为其建立一套独立的数据处理逻辑,包括数据源(DataSource),会话工厂(SqlSessionFactory),连接,DAO操作。
一、搭建springboot工程
项目工程结构如下所示,主要从实体类和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
三、配置数据源DataSource
新增数据源配置类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容器。
- MasterMybatisConfig.class:
@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();
}
}
- SlaveMybatisConfig.class:
@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。
五、建立多套实体、Mapper和xml
-
实体:
@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
,实现了此功能,所以我们实现动态数据源时继承它,实现自己的获取数据源的逻辑即可。
一、搭建springboot工程
├─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
三、配置数据源DataSource
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.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </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); } }
七、使用AOP切换数据源
上面我们通过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.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
-
定义切面:
- 注解 Pointcut 使用
annotation
指定注解 - 注解 Around 使用环绕通知处理,使用上下文进行对使用注解
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); } } }
- 注解 Pointcut 使用
-
基本使用:
创建
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); }
八、MyBatis-Plus动态数据源插件使用:
-
引入依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <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
的拷贝 - 自定义构造函数,把需要保存的目标数据源拷贝到自定义的 Map 中
- 添加新数据源时,依然使用
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); }