天天看点

Springboot 多数据源动态切换 以AOP切点方式实现

那么现在这篇是Springboot操作多数据源,我采用一贯的优雅方式实现:注解 ,切点的方式实现。

进入主题,

先看看这次案例项目的最终目录结构:

Springboot 多数据源动态切换 以AOP切点方式实现

然后我这次准备的两个不同的数据库(多个也可以),

一个是game_message , 一个是 game_message_cluster 。 

首先先看看我们这次用到的jar,pom.xml(相关jar的作用都有相关的注释):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mysql连接-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--Druid连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.9</version>
        </dependency>
        <!--整合mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!--AOP-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>      

然后接下来是application.yml文件:

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    druid:
      #第一个数据源连接信息
      one:
        url: jdbc:mysql://localhost:3306/game_message?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
        username: root
        password: root
      #第二个数据源连接信息
      two:
        url: jdbc:mysql://localhost:3306/game_message_cluster?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
        username: root
        password: root
      #数据库连接池信息
      initial-size: 10
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
#配置下项目端口
server:
  port: 8023
#mybatis扫描文件路径
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
#将日志文件生成到系统盘路径
logging:
  path: F:\\logtest\\log
  #简单设置一下日志等级
  level:
    web: debug      

接下来我们罗列下数据源的名字,这里简单用ONE 、TWO 表示,DataSourceNames.java:

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/

public interface DataSourceNames {
    String ONE = "ONE";
    String TWO = "TWO";
}      

接着创一个自定义注解,作为aop切点使用,DataSource.java:

import java.lang.annotation.*;

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value() default DataSourceNames.ONE; //默认值为ONE,因为后面我们选择配置这个ONE为默认数据库
}      

然后是配置AOP切点,DataSourceAspect.java:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
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;

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/
@Aspect
@Component
public class DataSourceAspect implements Ordered {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 切点: 所有配置 DataSource 注解的方法
     */
    @Pointcut("@annotation(com.example.demo.config.DataSource)")
    public void dataSourcePointCut() {}

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource ds = method.getAnnotation(DataSource.class);
        // 通过判断 DataSource 中的值来判断当前方法应用哪个数据源
        DynamicDataSource.setDataSource(ds.value());
        logger.info("AOP切换数据源成功,数据源为: " + ds.value());
        logger.info("set datasource is " + ds.value());
        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
            logger.info("clean datasource");
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}      

到这里切点AOP的相关配置已经完毕了,接下来到核心的动态数据源配置。

先创建手动切换数据源的核心方法类,DynamicDataSource.java:

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    /**
     * 配置DataSource, defaultTargetDataSource为主数据库
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }

}      

然后是用于读取配置信息多数据源,并将其载入的类,DynamicDataSourceConfig.java:

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/
@Configuration
public class DynamicDataSourceConfig {

    /**
     * 创建 DataSource Bean
     * */

    @Bean
    @ConfigurationProperties("spring.datasource.druid.one")
    public DataSource oneDataSource(){
        DataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.two")
    public DataSource twoDataSource(){
        DataSource dataSource = DruidDataSourceBuilder.create().build();
        return dataSource;
    }

    /**
     * 将数据源信息载入targetDataSources
     * */

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>(2);
        targetDataSources.put(DataSourceNames.ONE, oneDataSource);
        targetDataSources.put(DataSourceNames.TWO, twoDataSource);
        // 如果还有其他数据源,可以按照数据源one和two这种方法去进行配置,然后在targetDataSources中继续添加
        System.out.println("加载的数据源DataSources:" + targetDataSources);

        //DynamicDataSource(默认数据源,所有数据源) 第一个指定默认数据库
        return new DynamicDataSource(oneDataSource, targetDataSources);
    }
}      

然后是mapper层,MessageboardMapper.java:

import com.example.demo.pojo.Messageboard;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MessageboardMapper {


    Messageboard selectByPrimaryKey(Integer id);


}      

然后对应的MessageboardMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.demo.mapper.MessageboardMapper" >
  <resultMap id="BaseResultMap" type="com.example.demo.pojo.Messageboard" >
    <id column="id" property="id" jdbcType="INTEGER" />
    <result column="userName" property="username" jdbcType="VARCHAR" />
    <result column="message" property="message" jdbcType="VARCHAR" />
  </resultMap>
  <sql id="Base_Column_List" >
    id, userName, message
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    <include refid="Base_Column_List" />
    from messageboard
    where id = #{id,jdbcType=INTEGER}
  </select>

</mapper>      

mybatis-config.xml :

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <setting name="useGeneratedKeys" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>      

(想了解更多的mybatis配置项,

接下来是较为关键的,就是service层,简单创建一个DataSourceTestService.java:

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/

import com.example.demo.config.DataSource;
import com.example.demo.config.DataSourceNames;
import com.example.demo.mapper.MessageboardMapper;
import com.example.demo.pojo.Messageboard;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class DataSourceTestService {
    @Autowired
    private MessageboardMapper messageboardMapper;

    public Messageboard testMaster(Integer userId){
        return messageboardMapper.selectByPrimaryKey(userId);
    }

    @DataSource(DataSourceNames.TWO)
    public Messageboard testCluster(Integer userId){
        return messageboardMapper.selectByPrimaryKey(userId);
    }
}      

这里需要认真看,

@DataSource(DataSourceNames.TWO)      

这个注解标记的方法就是告诉当前方法即将切换数据源,所切换的数据源就是通过切点注解传值,方法当被调用时,就会从切点先进入AOP进行数据源切换设置。

最后,写一个简单的接口来进行多数据源操作测试,

我准备的两个数据库里面都有一张叫表messageboard的表,里面的数据不一样:

Springboot 多数据源动态切换 以AOP切点方式实现
Springboot 多数据源动态切换 以AOP切点方式实现

然后现在用接口调一下,TestController.java:

import com.example.demo.pojo.Messageboard;
import com.example.demo.service.DataSourceTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author : JCccc
 * @CreateTime : 2019/8/28
 * @Description :
 **/
@RestController
public class TestController {
    @Autowired
    DataSourceTestService dataSourceTestServiceImpl;

    @RequestMapping("/testDbSource")
    public void testDbSource() {
        Messageboard messageboard = dataSourceTestServiceImpl.testMaster(1);
        System.out.println(messageboard.toString());
        Messageboard messageboard2 = dataSourceTestServiceImpl.testCluster(1);
        System.out.println(messageboard2.toString());
        Messageboard messageboard3 = dataSourceTestServiceImpl.testMaster(1);
        System.out.println(messageboard3.toString());
    }

}      

将项目运行,可以看到控制台打印,数据源已经都加载了:

Springboot 多数据源动态切换 以AOP切点方式实现

用postman调下接口:

Springboot 多数据源动态切换 以AOP切点方式实现

继续阅读