SpringBoot+MyBatis使用AOP+注解实现多数据源

使用场景

一个应用程序需要链接多个数据库,比如读写分离架构下的读库和写库。

配置多数据源

数据库

创建数据库:ds1

CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

插入数据:

INSERT INTO `user` VALUES (1, 'ds1-1', 'ds1-1', 'ds1-1');
INSERT INTO `user` VALUES (2, 'ds1-2', 'ds1-2', 'ds1-2');
INSERT INTO `user` VALUES (3, 'ds1-3', 'ds1-3', 'ds1-3');

创建数据库:ds2

CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;

插入数据:

INSERT INTO `user` VALUES (1, 'ds2-1', 'ds2-1', 'ds2-1');
INSERT INTO `user` VALUES (2, 'ds2-2', 'ds2-2', 'ds2-2');
INSERT INTO `user` VALUES (3, 'ds2-3', 'ds2-3', 'ds2-3');

添加项目依赖


<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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.7.16version>
        <relativePath/> 
    parent>
    <groupId>com.chaoyinsugroupId>
    <artifactId>multiple-data-sourcesartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>multiple-data-sourcesname>
    <description>multiple-data-sourcesdescription>
    <properties>
        <java.version>1.8java.version>
    properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>2.3.1version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>


        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-devtoolsartifactId>
            <scope>runtimescope>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>com.mysqlgroupId>
            <artifactId>mysql-connector-jartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starter-testartifactId>
            <version>2.3.1version>
            <scope>testscope>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombokgroupId>
                            <artifactId>lombokartifactId>
                        exclude>
                    excludes>
                configuration>
            plugin>
        plugins>
    build>

project>

在YAML文件中定义数据源所需的数据

spring:
  datasource:
    mysql-datasource1:
      jdbc-url: jdbc:mysql://localhost:3306/ds1?useSSL=true&serverTimezone=Asia/Shanghai
      username: root
      password: 666666
      driver-class-name: com.mysql.cj.jdbc.Driver

    mysql-datasource2:
      jdbc-url: jdbc:mysql://localhost:3306/ds2?useSSL=true&serverTimezone=Asia/Shanghai
      username: root
      password: 666666
      driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:/mapper/*.xml

定义多个数据源

package com.chaoyinsu.config;

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 javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean(name = "mysqlDataSource1")
    @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource1")
    public DataSource dataSource1(){
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "mysqlDataSource2")
    @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource2")
    public DataSource dataSource2(){
        return DataSourceBuilder.create().build();
    }
}

SpringBoot启动类

package com.chaoyinsu;

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

@MapperScan("com.chaoyinsu.mapper")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})//禁用默认配置数据源
@EnableAspectJAutoProxy //开启Spring Boot对AOP的支持
public class MultipleDataSourcesApplication {

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

}

定义枚举来表示数据源的标识

public enum DataSourceType {

    MYSQL_DATASOURCE1,

    MYSQL_DATASOURCE2,

}

继承AbstractRoutingDataSource类

package com.chaoyinsu.config;

import com.chaoyinsu.common.DataSourceType;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Primary
public class DataSourceManagement extends AbstractRoutingDataSource {

    //使用ThreadLocal而不是String,可以在多线程的时候保证数据的可靠性
    public static ThreadLocal<String> flag = new ThreadLocal<>();

    @Resource
    private DataSource mysqlDataSource1; // 注入第一个数据源

    @Resource
    private DataSource mysqlDataSource2; // 注入第二个数据源


    public DataSourceManagement(){ // 使用构造方法初始化ThreadLocal的值
        flag.set(DataSourceType.MYSQL_DATASOURCE1.name());
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return flag.get();
    }

    @Override
    public void afterPropertiesSet() {
        Map<Object,Object> targetDataSource = new ConcurrentHashMap<>();
        targetDataSource.put(DataSourceType.MYSQL_DATASOURCE1.name(),mysqlDataSource1);
        targetDataSource.put(DataSourceType.MYSQL_DATASOURCE2.name(),mysqlDataSource2);
        super.setTargetDataSources(targetDataSource);
        super.setDefaultTargetDataSource(mysqlDataSource1);
        super.afterPropertiesSet();
    }
}

自定义注解

package com.chaoyinsu.common;

import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {

    DataSourceType value() default DataSourceType.MYSQL_DATASOURCE1;
}

定义注解的实现类

package com.chaoyinsu.common;

import com.chaoyinsu.config.DataSourceManagement;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
@Slf4j
public class TargetDataSourceAspect {


    @Before("@within(TargetDataSource) || @annotation(TargetDataSource)")
    public void beforeNoticeUpdateDataSource(JoinPoint joinPoint){
        TargetDataSource annotation = null;
        Class<? extends Object> target = joinPoint.getTarget().getClass();
        if(target.isAnnotationPresent(TargetDataSource.class)){
            // 判断类上是否标注着注解
             annotation = target.getAnnotation(TargetDataSource.class);
             log.info("类上标注了注解");
        }else{
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            if(method.isAnnotationPresent(TargetDataSource.class)){
                // 判断方法上是否标注着注解,如果类和方法上都没有标注,则报错
                annotation = method.getAnnotation(TargetDataSource.class);
                log.info("方法上标注了注解");
            }else{
                throw new RuntimeException("@TargetDataSource注解只能用于类或者方法上, 错误出现在:[" +
                        target.toString() +" " + method.toString() + "];");
            }
        }
        // 切换数据源
        DataSourceManagement.flag.set(annotation.value().name());
    }

}

实体类

package com.chaoyinsu.entity;

import lombok.Data;

/**
 * @author guochao
 * @version 1.0
 * @date 2023/10/17
 */
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String nickname;
}

UserService

package com.chaoyinsu.service;

import com.chaoyinsu.entity.User;
import com.chaoyinsu.mapper.UserMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author guochao
 * @version 1.0
 * @date 2023/10/17
 */
@Service
public class UserService {

    @Resource
    private UserMapper userMapper;


    public List<User> list() {
        return userMapper.list();
    }
}

UserMapper接口

package com.chaoyinsu.mapper;

import com.chaoyinsu.entity.User;

import java.util.List;

/**
 * @author guochao
 * @version 1.0
 * @date 2023/10/17
 */
public interface UserMapper {
    List<User> list();
}

UserMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.chaoyinsu.mapper.UserMapper" >

    <select id="list" resultType="com.chaoyinsu.entity.User">
        select * from `user`
    select>

mapper>

使用

package com.chaoyinsu.controller;// 访问第一个数据库的t_user表

import com.chaoyinsu.common.DataSourceType;
import com.chaoyinsu.common.TargetDataSource;
import com.chaoyinsu.config.DataSourceManagement;
import com.chaoyinsu.entity.User;
import com.chaoyinsu.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;

@RestController
public class UserController {

    @Resource
    private UserService userService;

    @GetMapping(value = "/user_list")
    public List<User> showUserList(){
        System.out.println(DataSourceType.MYSQL_DATASOURCE1.name());
        List<User> list = userService.list();
        return list;
    }

    @GetMapping(value = "/user_list2")
    // 将注解标注在方法上,表示此方法使用数据源2
    @TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE2)
    public List<User> showUserList2(){
        List<User> list = userService.list();
        return list;
    }

}


你可能感兴趣的:(spring,boot,mybatis,java)