springboot 配置多数据源(Aop+注解实现)

springboot 配置多数据源(Aop+注解实现)

在实际项目中很多时候会涉及到多个数据库的访问,或者数据库读写分离的形式。

下面通过使用 Aspect+注解来实现mybatis的多数据源配置

动态数据源流程说明

Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。而在 Spring 中,已提供了抽象类 AbstractRoutingDataSource 来实现此功能。因此,我们在实现动态数据源的,只需要继承它,实现自己的获取数据源逻辑即可。动态数据源流程如下所示:

在这里插入图片描述

用户访问应用,在需要访问不同的数据源时,根据自己的数据源路由逻辑,访问不同的数据源,实现对应数据源的操作。本示例中的两数据库的分别有一个表 t_user,表结构一致,为便于说明,两个表中的数据是不一样的。

实现动态数据源

1.数据库连接信息配置

在application.yml中添加如下信息,可根据自己的项目进行替换相应的prefix和连接信息。

springboot:
  datasource:
    master:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/spring?useUnicode=true&characherEncoding=utf-8&useSSL=true&serverTimezone=UTC
      username: root
      password: 123456
    slave:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/spring1?useUnicode=true&characherEncoding=utf-8&useSSL=true&serverTimezone=UTC
      username: root
      password: 123456

2.数据源配置

根据连接信息,把数据源注入到 Spring 中,添加 DynamicDataSourceConfig 文件,配置如下:

@Configuration
public class DynamicDataSourceConfig {
    @Bean(DataSourceConstants.DS_KEY_MASTER)
    @ConfigurationProperties(prefix = "springboot.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(DataSourceConstants.DS_KEY_SLAVE)
    @ConfigurationProperties(prefix = "springboot.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
}

DataSourceConstants类:

public class DataSourceConstants {
    public final static String DS_KEY_MASTER = "master";

    public final static String DS_KEY_SLAVE = "slave";
}

分别将master和slave数据源注入到spring容器中。

3.动态数据源设置

添加动态数据源类

DynamicDataSourceDynamicDataSourceContextHolder

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

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) {
        System.out.println("切换数据源"+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();
    }
}

3.设置动态数据源为主数据源

在前面的数据源配置文件 DynamicDataSourceConfig 中,添加以下代码:

@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;
}
  • 使用注解 Primary 优先从动态数据源中获取

  • 同时,需要在 DynamicDataSourceConfig 中,排除 DataSourceAutoConfiguration 的自动配置,否则 会出现The dependencies of some of the beans in the application context form a cycle的错误。在· DynamicDataSourceConfig 类上添加

    @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
    

DynamicDataSourceConfig 最终代码如下:

@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
public class DynamicDataSourceConfig {
    @Bean(DataSourceConstants.DS_KEY_MASTER)
    @ConfigurationProperties(prefix = "springboot.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(DataSourceConstants.DS_KEY_SLAVE)
    @ConfigurationProperties(prefix = "springboot.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;
    }
}

使用AOP选择数据源

1.自定义一个annotation

DbAnnotation注解,用于mapper接口上:

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface DbAnnotation {
    /**
     * 数据源名称,默认master
     */
    String value() default DataSourceConstants.DS_KEY_MASTER;
}

2.定义数据源切面

定义数据源切面,用于拦截添加了@DbAnnotation注解的类或方法:

添加maven依赖

<dependency>    
    <groupId>org.springframework.boot</groupId>    
    <artifactId>spring-boot-starter-aop</artifactId> 
</dependency>

定义切面DynamicDataSourceAspect类:

@Aspect
@Component
public class DynamicDataSourceAspect {
    //拦截DbAnnotation
    @Pointcut("@within(com.mj.vscodedemo.annotation.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注解,分别为master和slave数据源添加了个测试的mapper:

@Mapper
@Repository
@DbAnnotation(DataSourceConstants.DS_KEY_MASTER)
public interface UserMapper {
    Integer getUserId();
}

@Mapper
@Repository
@DbAnnotation(DataSourceConstants.DS_KEY_SLAVE)
public interface UserMapper1 {
    Integer getUserId();
}

多数据源配置已经完成了,下面就可以进行调用测试访问了

测试

添加一个controller并直接调用mapper的方法

@RestController
public class HelloController {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserMapper1 userMapper1;

    @ApiOperation(value = "sayHi")
    @GetMapping("/sayHi")
    public String sayHi() {
        int i = userMapper.getUserId();
        System.out.println("用户1数量" + i);
        int i2 = userMapper1.getUserId();
        System.out.println("用户2数量" + i2);
        return "你好啊";
    }
}

输出如下:

切换数据源master
2020-11-29 22:44:58.933  INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-11-29 22:45:00.451  INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
用户1数量1
切换数据源slave
2020-11-29 22:45:00.664  INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2020-11-29 22:45:00.728  INFO 13292 --- [nio-8090-exec-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
用户2数量2

可以看到分别访问的是master和slave的数据源。

下面就可以进行db读写分离愉快的写代码了。

最后感谢这篇文章的博主,写的很详细,地址https://www.cnblogs.com/masonlee/p/12207853.html

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值