springboot AOP实现多数据源动态切换

有3个数据源
t100019、t100049、t100138
本文旨在使用springboot、AOP、AbstractRoutingDataSource实现多数据源的动态切换

1.导入数据源、aop等相关依赖

 <properties>
        <java.version>1.8</java.version>
        <aspectjrt.version>1.9.1</aspectjrt.version>
        <aspectjweaver.version>1.9.1</aspectjweaver.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-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.13</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.0.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectjrt.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>
    </dependencies>

2.配置文件配置如下

server.port=8889

spring.datasource.t100019.username=root
spring.datasource.t100019.password=root
spring.datasource.t100019.url=jdbc:mysql://127.0.0.1:3306/t100019?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.t100019.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.t100019.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.t100049.username=root
spring.datasource.t100049.password=root
spring.datasource.t100049.url=jdbc:mysql://127.0.0.1:3306/t100049?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.t100049.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.t100049.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.t100138.username=root
spring.datasource.t100138.password=root
spring.datasource.t100138.url=jdbc:mysql://127.0.0.1:3306/t100138?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.t100138.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.t100138.type=com.alibaba.druid.pool.DruidDataSource

3.准备数据库表

建3个数据库,分别是t100019,t100049,t100138;分别执行如下sql,创建hy_floors表。3个库可插入不同数量数据,以便于之后切换数据源查看差异

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for hy_floors
-- ----------------------------
DROP TABLE IF EXISTS `hy_floors`;
CREATE TABLE `hy_floors`  (
  `id` int(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
  `project_id` bigint(0) NOT NULL COMMENT '项目编号',
  `park_id` int(0) NOT NULL COMMENT '园区id',
  `building_id` int(0) NOT NULL COMMENT '楼建筑物ID',
  `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '名称(同项目名称)',
  `area_covered` float NULL DEFAULT NULL COMMENT '占地面积',
  `floor_no` int(0) NULL DEFAULT NULL COMMENT '层序号',
  `underground_number` int(0) NULL DEFAULT NULL COMMENT '地下层数(废弃)',
  `owners` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '业主编号{JSON}',
  `developers` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开发商{JSON}',
  `operators` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '运营商{JSON}',
  `code` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编码(900101xxxxxx)',
  `image` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '平面图',
  `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  `deleteflag` int(0) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1209 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '建筑楼层表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of hy_floors
-- ----------------------------
INSERT INTO `hy_floors` VALUES (1, 123456789, 12, 212, 'B1', 5.6, 1, NULL, '', '', '', '90.90.90.90.90.90', NULL, '', 0);
INSERT INTO `hy_floors` VALUES (2, 123456789, 12, 212, 'B1', 5.3, 2, NULL, '', '', '', '90.90.90.90.90.90', NULL, '', 0);
INSERT INTO `hy_floors` VALUES (3, 123456789, 12, 212, 'B1', 3.2, 3, NULL, '', '', '', '90.90.90.90.90.90', NULL, '', 0);
INSERT INTO `hy_floors` VALUES (4, 123456789, 12, 212, 'B1', 3, 4, NULL, '', '', '', '90.90.48.90.90.90', NULL, '', 0);
INSERT INTO `hy_floors` VALUES (5, 123456789, 12, 212, 'B1', 3, 5, NULL, '', '', '', '90.90.90.90.90.90', NULL, '', 0);
INSERT INTO `hy_floors` VALUES (6, 123456789, 12, 212, 'B1', 3, 6, NULL, '', '', '', '90.90.90.90.90.90', NULL, '', 0);
INSERT INTO `hy_floors` VALUES (7, 123456789, 12, 212, 'B1', NULL, 7, NULL, '', '', '', '90.90.90.90.90.90', NULL, '', 0);

SET FOREIGN_KEY_CHECKS = 1;

4.写一个数据库名称的枚举类 DbTypeEnum

package com.example.dynamicdatasource.enums;

/**
 *
 * Title: DbTypeEnum
 * Description: 数据库枚举类
 */
public enum DbTypeEnum {
    T100019("t100019"), T100049("t100049"), T100138("t100138");

    private String value;

    DbTypeEnum(final String value) {

        this.value = value;
    }

    public String getValue() {

        return value;
    }
}

5.建立一个获得和设置上下文环境的类 DbContextHolder ,主要负责设置和获取上下文数据源的名称

package com.example.dynamicdatasource.config;

import com.example.dynamicdatasource.enums.DbTypeEnum;

public class DbContextHolder {

    private static final ThreadLocal contextHolder = new ThreadLocal<>();

    /**
     * 设置数据源
     * 
     * @param dbTypeEnum
     */
    public static void setDbType(final DbTypeEnum dbTypeEnum) {

        contextHolder.set(dbTypeEnum.getValue());
    }

    /**
     * 取得当前数据源
     * 
     * @return
     */
    public static String getDbType() {

        return (String) contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {

        contextHolder.remove();
    }
}

6.写一个动态数据源类,继承AbstractRoutingDataSource,并重写determineCurrentLookupKey()方法。返回值就是要路由的数据源的名称

package com.example.dynamicdatasource.config;

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

public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 取得当前使用哪个数据源
     * 
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {

        return DbContextHolder.getDbType();
    }
}

7.写数据源配置类DataSourceConfig

package com.example.dynamicdatasource.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.example.dynamicdatasource.enums.DbTypeEnum;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

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

@Configuration
@MapperScan("com.example.dynamicdatasource.*.dao")
public class DataSourceConfig {

    @Bean(name = "t19DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.t100019")
    public DataSource t19DataSource() {

        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "t49DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.t100049")
    public DataSource t49DataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "t138DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.t100138")
    public DataSource t138DataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 动态数据源配置
     *
     * @return
     */
    @Bean("multipleDataSource")
    @Primary
    public DynamicDataSource multipleDataSource(@Qualifier("t19DataSource") final DataSource t19DataSource,
                                                @Qualifier("t49DataSource") final DataSource t49DataSource,
                                                @Qualifier("t138DataSource") final DataSource t138DataSource) {

        final DynamicDataSource dynamicDataSource = new DynamicDataSource();
        final Map<Object, Object> targetDataSources = new HashMap<>(3);
        targetDataSources.put(DbTypeEnum.T100019.getValue(), t19DataSource);
        targetDataSources.put(DbTypeEnum.T100049.getValue(), t49DataSource);
        targetDataSources.put(DbTypeEnum.T100138.getValue(), t138DataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(t19DataSource);
        return dynamicDataSource;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(multipleDataSource(t19DataSource(), t49DataSource(), t138DataSource()));
        //设置mapperLocation
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*/*Mapper.xml"));
        //设置开启驼峰匹配
        sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
        return sqlSessionFactoryBean.getObject();
    }

    @Bean("transactionManager")
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("multipleDataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

}

8.AOP写数据源动态切换切面

package com.example.dynamicdatasource.aop;

import com.example.dynamicdatasource.config.DbContextHolder;
import com.example.dynamicdatasource.enums.DbTypeEnum;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class DataSourceSwitchAspect {

    @Pointcut("execution(* com.example.dynamicdatasource.t100019.dao..*.*(..))")
    private void t19Aspect() {
    }
    //切入点
    @Pointcut("execution(* com.example.dynamicdatasource.t100049.dao..*.*(..))")
    private void t49dbDictAspect() {
    }

    @Pointcut("execution(* com.example.dynamicdatasource.t100138.dao..*.*(..))")
    private void t138dbPartnerAspect() {
    }
    //前置通知,调对应路径下方法时拦截并设置数据源
    @Before("t19Aspect()")
    public void t19() {
        DbContextHolder.setDbType(DbTypeEnum.T100019);
    }

    @Before("t49dbDictAspect()")
    public void t49() {
        DbContextHolder.setDbType(DbTypeEnum.T100049);
    }

    @Before("t138dbPartnerAspect()")
    public void t138() {
        DbContextHolder.setDbType(DbTypeEnum.T100138);
    }
}

9.为方便测试,写一个最简单的SSM。t100019包下的SSM代码 controller层,t100049和t100138如是。

package com.example.dynamicdatasource.t100019.controller;

import com.example.dynamicdatasource.t100019.service.T19Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class T19Controller {

    @Autowired
    private T19Service t19Service;
    
    @GetMapping("/test/19")
    public Integer getCount() {
        return t19Service.getCount();
    }

}

service层

package com.example.dynamicdatasource.t100019.service;

public interface T19Service {
    Integer getCount();
}

package com.example.dynamicdatasource.t100019.service.impl;

import com.example.dynamicdatasource.t100019.dao.T19Dao;
import com.example.dynamicdatasource.t100019.service.T19Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class T19ServiceImpl implements T19Service {
    @Autowired
    private T19Dao t19Dao;
    @Override
    public Integer getCount() {
        return t19Dao.getCount();
    }
}

dao层

package com.example.dynamicdatasource.t100019.dao;
public interface T19Dao {
    Integer getCount();
}

mapper

<?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.dynamicdatasource.t100019.dao.T19Dao">
    <select id="getCount" resultType="java.lang.Integer">
       SELECT COUNT(id) FROM `hy_floors`
    </select>
</mapper>

t100049和t100138包下代码与t100019代码相同,只是名称第一不同。整体代码结构如下
在这里插入图片描述

测试:

在这里插入图片描述
t100019库
在这里插入图片描述
t100049库
在这里插入图片描述
t100138库
在这里插入图片描述

项目源码地址:

https://github.com/sidney189/dynamicDataSource.git

本质:

AbstractRoutingDataSource, 该类充当了DataSource的路由中介, 能在运行时, 根据某个key值来动态切换到真正的DataSource上。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值