springboot整合mybatis实现双数据源切换

导入jar包

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

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

 配置文件信息

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 主数据源
      master-db:
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&characterEncoding=utf-8
        username: root
        password: 123456
      # 从数据源
      slave-db:
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&characterEncoding=utf-8
        username: root
        password: 123456
      max-active: 20 # 最大连接池数量
      initial-size: 5 # 初始化建立物理连接个数
      min-idle: 5 # 最小连接池数量
      max-wait: 3000 # 获取连接时最大等待时间(毫秒)
mybatis:
  mapper-locations: classpath:mapper/*.xml

 数据源配置

package com.wuhj.config;

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 org.springframework.stereotype.Component;

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

/**
 * @Description: 数据源配置
 * @Author wuhj
 * @Date 2021/4/2 11:18
 */
@Configuration
@Component
public class DynamicDataSourceConfig {

    //读取application配置,构建master-db数据源
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master-db")
    public DataSource myMasterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    //读取application配置,构建slave-db数据源
    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave-db")
    public DataSource mySlaveDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    //读取application配置,创建动态数据源
    @Bean
    @Primary
    public DynamicDataSource dynamicDataSource(DataSource myMasterDataSource, DataSource mySlaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master-db", myMasterDataSource);
        targetDataSources.put("slave-db", mySlaveDataSource);
        // myTestDbDataSource 默认数据源
        // targetDataSources  目标数据源(多个)
        return new DynamicDataSource(myMasterDataSource, targetDataSources);
    }
}

数据源动态切换

package com.wuhj.config;

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

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

/**
 * @Description: 数据源动态切换
 * @Author wuhj
 * @Date 2021/4/2 11:06
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    // 通过ThreadLocal维护一个全局唯一的map来实现数据源的动态切换
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    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();
    }
}

 自定义注解MyDataSource,结合AOP实现数据源动态切换

package com.wuhj.annotation;

import java.lang.annotation.*;

/**
 * @Description: 自定义数据源注解
 * @Author wuhj
 * @Date 2021/4/2 11:23
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
    String name() default "";
}

DataSourceAspect切面类,以MyDataSource注解为切入点。在执行被@MyDataSource注解的方法时,获取该注解传入的name,并切换到指定数据源执行,执行完成后切换回默认数据源

package com.wuhj.aspect;

import com.wuhj.config.DynamicDataSource;
import com.wuhj.annotation.MyDataSource;
import lombok.extern.slf4j.Slf4j;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Description: 数据源切面
 * @Author wuhj
 * @Date 2021/4/2 11:27
 */
@Aspect
@Component
@Slf4j
public class DataSourceAspect {

    private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    //只有调用@MyDataSource注解的方法才会触发around
    @Pointcut("@annotation(com.wuhj.annotation.MyDataSource)")
    public void dataSourcePointCut() {
    }

    //截取使用MyDataSource注解的方法,切换指定数据源
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        logger.info("execute DataSourceAspect around=========>"+method.getName());
        // 1. 获取自定义注解MyDataSource,查看是否配置指定数据源名称
        MyDataSource dataSource = method.getAnnotation(MyDataSource.class);
        if(dataSource == null){
            logger.info("默认数据源");
            // 1.1 使用默认数据源
            DynamicDataSource.setDataSource("master-db");
        }else {
            // 1.2 使用指定名称数据源
            DynamicDataSource.setDataSource(dataSource.name());
            logger.info("使用指定名称数据源=========>"+dataSource.name());
        }
        try {
            return point.proceed();
        } finally {
            // 后置处理 - 恢复默认数据源
            logger.info("恢复数据源");
            DynamicDataSource.clearDataSource();
        }
    }
}

配置启动类 

package com.wuhj;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

/**
 * @Description:
 * @Author wuhj
 * @Date 2021/4/2 11:06
 */
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//不加载默认数据源配置
@MapperScan(basePackages = {"com.wuhj.mapper"})
public class MultipleDataSourceApplication {

    public static void main(String[] args) {
        SpringApplication.run(MultipleDataSourceApplication.class, args);
    }

}

User对象

package com.wuhj.bean;

import lombok.Data;

/**
 * @Description:
 * @Author wuhj
 * @Date 2021/4/2 11:41
 */
@Data
public class User {

    private long id;

    private String name;

    private int age;
}

 Controller层

package com.wuhj.controller;

import com.wuhj.bean.User;
import com.wuhj.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Description:
 * @Author wuhj
 * @Date 2021/4/2 11:38
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/master")
    public List<User> testMaster(){
        List<User> userList = userService.findMasterAll();
        return userList;

    }

    @GetMapping("/slave")
    public List<User> testSlave(){

        List<User> userList = userService.findSlaveAll();
        return userList;

    }


}

 Service层

package com.wuhj.service;

import com.wuhj.annotation.MyDataSource;
import com.wuhj.bean.User;
import com.wuhj.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Description:
 * @Author wuhj
 * @Date 2021/4/2 11:38
 */
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    //使用默认数据源
    public List<User> findMasterAll() {
        return userMapper.findUserAll();
    }

    //使用@MyDataSource注解切换数据源
    @MyDataSource(name = "slave-db")
    public List<User> findSlaveAll() {
        return userMapper.findUserAll();
    }
}

 Mapper层

package com.wuhj.mapper;

import com.wuhj.bean.User;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @Description:
 * @Author wuhj
 * @Date 2021/4/2 11:35
 */
@Repository
public interface UserMapper {

    List<User> findUserAll();

}

 UserMapper.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.wuhj.mapper.UserMapper">
    <!--查询user信息-->
    <select id="findUserAll" resultType="com.wuhj.bean.User">
        select * from user;
    </select>

</mapper>

 数据库信息,创建数据库test和test2,分别执行脚本建表并插入测试数据

/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 80023
Source Host           : localhost:3306
Source Database       : test

Target Server Type    : MYSQL
Target Server Version : 80023
File Encoding         : 65001

Date: 2021-04-07 10:07:18
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint NOT NULL,
  `name` varchar(32) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'zhangsan', '14');
INSERT INTO `user` VALUES ('2', 'lisi', '13');
INSERT INTO `user` VALUES ('3', 'wangwu', '16');
/*
Navicat MySQL Data Transfer

Source Server         : localhost
Source Server Version : 80023
Source Host           : localhost:3306
Source Database       : test2

Target Server Type    : MYSQL
Target Server Version : 80023
File Encoding         : 65001

Date: 2021-04-07 10:08:22
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint NOT NULL,
  `name` varchar(32) DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('4', 'sunliu', '9');
INSERT INTO `user` VALUES ('5', 'gouqi', '15');
INSERT INTO `user` VALUES ('6', 'maoba', '13');

测试结果

接口地址:http://localhost:9999/user/master
返回结果:[{"id":1,"name":"zhangsan","age":14},{"id":2,"name":"lisi","age":13},{"id":3,"name":"wangwu","age":16}]


接口地址:http://localhost:9999/user/slave
返回结果:[{"id":4,"name":"sunliu","age":9},{"id":5,"name":"gouqi","age":15},{"id":6,"name":"maoba","age":13}]

结论:通过自定义注解和AOP实现了数据源动态切换

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值