一个应用程序需要链接多个数据库,比如读写分离架构下的读库和写库。
创建数据库: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>
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();
}
}
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,
}
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;
}
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();
}
}
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();
}
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;
}
}