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

java1234

共 7944字,需浏览 16分钟

 · 2020-12-04

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

66套java从入门到精通实战课程分享

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

下面通过使用 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.动态数据源设置

添加动态数据源类

DynamicDataSource 和 DynamicDataSourceContextHolder

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 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 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 })
    • 1

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 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依赖

    
    org.springframework.boot    
    spring-boot-starter-aop 


定义切面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读写分离愉快的写代码了。



版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:

https://blog.csdn.net/mj_940620/article/details/110356785






粉丝福利:实战springboot+CAS单点登录系统视频教程免费领取

???

?长按上方微信二维码 2 秒
即可获取资料



感谢点赞支持下哈 

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报