java springboot动态数据源配置

2020-08-12  本文已影响0人  进击的奥莉

听说爱点赞的人都月瘦十斤,月入十万哟!
自从接触了JAVA,真的是每天都学到新知识,很具有挑战性,今天开发过程中,springboot项目需要连接mysql数据库和sql server数据库.通过查资料,终于完成了接口测试,记录一下,以便帮助更多的人.
通过百度查询 关键字:springboot 多数据源,给出的例子基本都是多套源策略,什么是多套源呢,看下图:

多套源策略1.png
这种策略虽然简单、直接、好理解,也符合开闭原则(再添加数据库,原来的数据库信息不用修改,只添加即可)
但是资源浪费,代码冗余,缺乏灵活。需要针对每一个数据源写一套操作,不推荐使用。
今天我来介绍使用AOP切面进行动态数据源配置。用户可以根据实际业务需要,统一操作逻辑,只需要在切换数据源的地方进行切换即可,超级方便。流程图如下:
动态数据源2.png

今天举例连接mysql数据库和sql server数据库

1,包结构说明

|--common
      |--annotation   //自定义注解
      |--aop     //切面
      |--context   //自定义
|--config    //数据源配置
|--controller    //访问接口
|--entity   //实体类
|--mapper  //数据库操作
|--service    //服务类
      |--impl     //实现类

2,pom.xml 引入相关包。

其中sqlserver引入jar包比较特殊,不能直接maven引入,需要自己下载,并添加到本地maven默认地址,sqljdbc4:jar:4.0问题解决方案

      <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.47</version>
        </dependency>
<!-- SqlServer -->
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>sqljdbc4</artifactId>
            <version>4.0</version>
        </dependency>
<!-- 切面 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3,接下来就是配置数据库信息了,在application.yml中添加

注:这里可以看出mysql和sqlserver的配置是不同的。数据库名引入不同。
mysql是端口号/book_test,
sqlserver是端口号;DatabaseName=project

spring:值对应
  datasource:
    master1:
      driver-class-name: com.mysql.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/book_test?useUnicode=true&useSSL=false&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
      username: ***
      password: ***
    master2:
      driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
      jdbc-url: jdbc:sqlserver://****:1433;DatabaseName=project
      username: **
      password: **

4,前期准备工作已完成,接下来进行动态数据源配置

4.1数据源配置:根据连接信息把数据源注入到spring中.config/DynamicDataSourceConfig.java
package com.springboot.test.config;

/**
 * @date: Created in 2020/8/12 13:36
 * @description: 动态数据源配置
 * @version: 1.0
 */
//import me.mason.demo.dynamicdatasource.constants.DataSourceConstants;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

// 添加此配置,否则 报`The dependencies of some of the beans in the application context form a cycle`
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
@Configuration
@PropertySource("classpath:application.yml")
@MapperScan(basePackages = "com.springboot.test.mapper")
public class DynamicDataSourceConfig {

    @Bean("master1")
    @ConfigurationProperties(prefix = "spring.datasource.master1")
    public DataSource master1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("master2")
    @ConfigurationProperties(prefix = "spring.datasource.master2")
    public DataSource master2DataSource() {
        return DataSourceBuilder.create().build();
    }

    //设置动态数据源为主数据源
    @Bean
    @Primary
    public DataSource dynamicDataSource() {
        Map<Object, Object> dataSourceMap = new HashMap<>(2);
        dataSourceMap.put("master1", master1DataSource());
        dataSourceMap.put("master2", master2DataSource());
        //设置动态数据源
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        dynamicDataSource.setDefaultTargetDataSource(master1DataSource());

        return dynamicDataSource;
    }

}

4.2 添加动态数据源类.config/DynamicDataSource.java

package com.springboot.test.config;

import com.springboot.test.common.context.DynamicDataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @date: Created in 2020/8/12 14:07
 * @description: 动态数据源
 * @version: 1.0
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getContextKey();
    }
}

4.3动态返回数据源。contxt/DynamicDataSourceContextHolder.java

package com.springboot.test.common.context;

/**
 * @date: Created in 2020/8/12 14:02
 * @description: TODO
 * @version: 1.0
 */
public class DynamicDataSourceContextHolder {

    /**
     * 动态数据源名称上下文
     */
    private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();

    /**
     * 设置数据源
     * @param key
     */
    public static void setContextKey(String key){
        System.out.println("切换数据源"+key);
        DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
    }

    /**
     * 获取数据源名称
     * @return
     */
    public static String getContextKey(){
        String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
        return key == null?"master1":key;
    }

    /**
     * 删除当前数据源名称
     */
    public static void removeContextKey(){
        DATASOURCE_CONTEXT_KEY_HOLDER.remove();
    }
}

4.3 定义数据源注解,这里很关键哟,定义了注解,之后再server层就可以直接@注解使用了.common/annotation/DS.java。不要问为什么把注解定义为java,因为大家都这样做,DS也是DataSource的简称。

package com.springboot.test.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @date: Created in 2020/8/12 13:55
 * @description: TODO
 * @version: 1.0
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
    /**
     * 数据源名称
     * @return
     */
    String value() default "master1";
}

4.4 定义切面

package com.springboot.test.common.aop;

import com.springboot.test.common.annotation.DS;
import com.springboot.test.common.context.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @date: Created in 2020/8/12 14:00
 * @description: TODO
 * @version: 1.0
 */
@Aspect
@Component
public class DynamicDataSourceAspect {
    @Pointcut("@annotation(com.springboot.test.common.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();
        }
    }

    /**
     * 根据类或方法获取数据源注解
     * @param joinPoint
     * @return
     */
    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);
        }
    }
}

5,接下来就是service层使用AOP进行数据源切换了。

在service/impl/TestUserServiceImpl.java页面

/**
     * 查询master1库User
     * @return
     */
    @DS("master1") //这个是自定义注解
    public List<TestUser> getMasterUser(){
        QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>();
        return testUserMapper.selectAll(queryWrapper.isNotNull("name"));
    }

    /**
     * 查询master2库User
     * @return
     */
    @DS("master2")  //这个是自定义注解
    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("master1", masterUser);
        //从slave数据源查询
        List<TestUser> slaveUser = testUserService.getSlaveUser();
        result.put("master2", slaveUser);
        //返回数据
        return ResponseResult.success(result);
    }

看,是不是超级简单?没有数据库切换代码,只需要关注业务逻辑即可,省心好多。你学会了吗?

原文地址:https://segmentfault.com/a/1190000021613404

上一篇 下一篇

猜你喜欢

热点阅读