使用场景
一个应用程序需要链接多个数据库,比如读写分离架构下的读库和写库。
配置多数据源
数据库
创建数据库: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');
添加项目依赖
<?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.7.16</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.chaoyinsu</groupId><artifactId>multiple-data-sources</artifactId><version>0.0.1-SNAPSHOT</version><name>multiple-data-sources</name><description>multiple-data-sources</description><properties><java.version>1.8</java.version></properties><dependencies><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.3.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter-test</artifactId><version>2.3.1</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
在YAML文件中定义数据源所需的数据
spring:datasource:mysql-datasource1:jdbc-url: jdbc:mysql://localhost:3306/ds1?useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: 666666driver-class-name: com.mysql.cj.jdbc.Drivermysql-datasource2:jdbc-url: jdbc:mysql://localhost:3306/ds2?useSSL=true&serverTimezone=Asia/Shanghaiusername: rootpassword: 666666driver-class-name: com.mysql.cj.jdbc.Drivermybatis: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<>();@Resourceprivate DataSource mysqlDataSource1; // 注入第一个数据源@Resourceprivate DataSource mysqlDataSource2; // 注入第二个数据源public DataSourceManagement(){ // 使用构造方法初始化ThreadLocal的值flag.set(DataSourceType.MYSQL_DATASOURCE1.name());}@Overrideprotected Object determineCurrentLookupKey() {return flag.get();}@Overridepublic 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 {@Resourceprivate 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
<?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.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 {@Resourceprivate 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;}}