1.aop+注解方式
在实际项目中很多时候会涉及到多个数据库的访问,或者数据库读写分离的形式。
下面通过使用 Aspect+注解来实现mysql+oracle的多数据源配置(注意:事务一致性未提供)
首先要去oracle官网下载ojdbc的jar包,根据oracle的版本去下载,或者在下载的oracle的jdbc包下的lib里面有,然后导入项目中!!!
动态数据源流程说明
Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。而在 Spring 中,已提供了抽象类 AbstractRoutingDataSource 来实现此功能。因此,我们在实现动态数据源的,只需要继承它,重写determineCurrentLookupKey的方法即可
代码如下:
1.数据库连接信息配置
在application.yml中添加如下信息,根据自己项目的需要进行替换
spring:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/数据库名
username: root
password: root
slave:
type: com.alibaba.druid.pool.DruidDataSource
platform: oracle
jdbc-url: jdbc:oracle:thin:@localhost:1521:数据库名
username: system
password: system
#指定数据源的全限定名
driver-class-name: oracle.jdbc.driver.OracleDriver
注意:根据springboot的版本不同,有不同的写法,在springboot2.0的版本中,注意url要写成jdbc-url,尝试过url的话会报错
2.数据源配置
根据连接信息,把数据源注入到 Spring 中,添加 DynamicDataSourceConfig
文件,配置如下
@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
public class DynamicDataSourceConfig {
@Bean(DataSourceConstants.MASTER)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(DataSourceConstants.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.MASTER, masterDataSource());
dataSourceMap.put(DataSourceConstants.SLAVE, slaveDataSource());
//设置动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
}
需要在 DynamicDataSourceConfig 中,排除 DataSourceAutoConfiguration 的自动配置,否则 会出现The dependencies of some of the beans in the application context form a cycle的错误
使用注解 Primary 优先从动态数据源中获取
DataSourceConstants
类:
public class DataSourceConstants {
public final static String MASTER = "master";
public final static String SLAVE = "slave";
}
3.动态数据源设置
添加动态数据源类
DynamicDataSource
和 DynamicDataSourceContextHolder
类
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getContextKey();
}
}
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.MASTER : key;
}
/*删除当前数据源名称*/
public static void removeContextKey() {
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
}
4.使用AOP选择数据源
自定义一个annotation DbAnnotation
注解
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DbAnnotation {
/**
* 数据源名称,默认master
*/
String value() default DataSourceConstants.MASTER;
}
定义数据源切面
@Aspect
@Component
public class DynamicDataSourceAspect {
//拦截DbAnnotation
@Pointcut("@within(com.example.constant.DbAnnotation)"+"||@annotation(com.example.constant.DbAnnotation)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsKey = this.getDSAnnotation(joinPoint).value();
DynamicDataSourceContextHolder.setContextKey(dsKey);
try {
return joinPoint.proceed();
} catch (Exception ex) {
throw ex;
} finally {
DynamicDataSourceContextHolder.removeContextKey();
}
}
/**
* 根据类或方法获取数据源注解
*/
private DbAnnotation getDSAnnotation(ProceedingJoinPoint joinPoint) {
//mybatis生成的代理类,所以获取它的接口来获取DbAnnotation注解信息
Class<?> targetClass = joinPoint.getTarget().getClass().getInterfaces()[0];
DbAnnotation dsAnnotation = targetClass.getAnnotation(DbAnnotation.class);
// 先判断类的注解,再判断方法注解
if (Objects.nonNull(dsAnnotation)) {
return dsAnnotation;
} else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
DbAnnotation annotation = methodSignature.getMethod().getAnnotation(DbAnnotation.class);
return annotation;
}
}
}
在mapper接口上添加@DbAnnotation
注解,为slave数据源添加了个测试的mapper:
@Mapper
public interface OrUserMapper extends BaseMapper<OrUser> {
@Select("select *from tb_user")
@DbAnnotation(DataSourceConstants.SLAVE)
List<OrUser> select();
}
@RequestMapping("/user/select")
public void test(){
List<OrUser> select = orUserMapper.select();
System.out.println(select.get(0));
}
然后如果想要在同一个函数里面调用不同的数据库的话,此时不能将注解定义在函数上面,不然mybatis-plus会报错,因为mybatis-plus 默认来说是运行期间就只支持一种数据源,初始化的时候就已经决定了,无法动态转换。
所以我们可以在一个方法里面需要调用不同数据库的地方的函数或者mapper接口那边定义使用的数据源即可
比如在mysql的mapper上面加上注解:
@Mapper
//可以加或者不加也可以
@DbAnnotation(DataSourceConstants.MASTER)
public interface UserMapper extends BaseMapper<User> {
}
其实加和不加都可以,因为不加的话使用的是默认的数据源, 默认的就是mysql
在使用oracle数据库的mapper里面
@Mapper
public interface OrUserMapper extends BaseMapper<OrUser> {
@Select("select *from tb_user")
@DbAnnotation(DataSourceConstants.SLAVE)
List<OrUser> select();
}
测试:
public void select() {
List<OrUser> select = orUserMapper.select();
List<User> users = userMapper.selectList(null);
System.out.println(select.get(0));
System.out.println(users.get(0));
}
2.使用spring提供的一个依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
配置文件:
spring:
datasource:
dynamic:
primary: master # 配置默认数据库
datasource:
#主库数据源
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/数据库名
username: root
password: root
slave:
type: com.alibaba.druid.pool.DruidDataSource
platform: oracle
url: jdbc:oracle:thin:@localhost:1521:数据库名
username: system
password: system
#指定数据源的全限定名
driver-class-name: oracle.jdbc.driver.OracleDriver
然后在需要切换的地方使用@DS注解即可
@DS("slave")
public void test(){
List<OrUser> select = userMapper.select();
System.out.println(select.get(0).getUsername());
}