Spring Boot 实现多数据源配置

一、配置流程

在 Spring Boot 中实现多数据源配置通常用于需要连接多个数据库的场景。主要有以下几个步骤:

  1. 配置多个数据源的连接信息。
  2. 定义多个数据源的 Bean。
  3. 为每个数据源配置MyBatis的SqlSessionFactory和事务管理器。
  4. 为每个数据源定义Mapper接口和Mapper XML文件。
  5. 在服务类中使用不同的Mapper接口操作对应的数据源。

二、详细步骤

2.1 添加依赖

首先,在pom.xml中添加MyBatis、数据库驱动和Spring Boot的JDBC依赖。

bash 复制代码
<dependencies>
    <!-- Spring Boot Starter Web (可选,如果需要使用Web功能) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- MyBatis Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- Spring Boot Starter JDBC -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
</dependencies>
2.2 配置多数据源

在application.properties或application.yml中配置多个数据源的连接信息。

bash 复制代码
spring:
  datasource:
    primary:
      jdbc-url: jdbc:mysql://localhost:3306/db1
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      jdbc-url: jdbc:mysql://localhost:3306/db2
      username: root
      password: password
      driver-class-name: com.mysql.cj.jdbc.Driver
2.3 配置数据源Bean

在Spring Boot中,通过@Configuration类来配置多个数据源的Bean。

bash 复制代码
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 org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    // 主数据源
    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    // 次数据源
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}
2.4 配置MyBatis的SqlSessionFactory和事务管理器

为每个数据源配置独立的SqlSessionFactory和DataSourceTransactionManager。

  • PrimaryDataSourceConfig .java

    bash 复制代码
    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.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;
    
    @Configuration
    @MapperScan(basePackages = "com.example.mapper.primary", sqlSessionFactoryRef = 	"primarySqlSessionFactory")
    public class PrimaryDataSourceConfig {
    
    	@Bean(name = "primarySqlSessionFactory")
    	@Primary
    	public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        	SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        	sessionFactory.setDataSource(dataSource);
        	// 设置MyBatis的Mapper XML文件路径
        	sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                	.getResources("classpath:mapper/primary/*.xml"));
        	return sessionFactory.getObject();
    	}
    
    	@Bean(name = "primaryTransactionManager")
    	@Primary
        public DataSourceTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        	return new DataSourceTransactionManager(dataSource);
    	}
    }
  • SecondaryDataSourceConfig.java

    bash 复制代码
    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.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    
    import javax.sql.DataSource;
    
    @Configuration
    @MapperScan(basePackages = "com.example.mapper.secondary", sqlSessionFactoryRef = "secondarySqlSessionFactory")
    public class SecondaryDataSourceConfig {
    
    	@Bean(name = "secondarySqlSessionFactory")
    	public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
        	SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        	sessionFactory.setDataSource(dataSource);
        	// 设置MyBatis的Mapper XML文件路径
        	sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                	.getResources("classpath:mapper/secondary/*.xml"));
        	return sessionFactory.getObject();
    	}
    
    	@Bean(name = "secondaryTransactionManager")
    	public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
        	return new DataSourceTransactionManager(dataSource);
    	}
    }
2.5 配置Mapper接口和XML文件

将Mapper接口和XML文件分别放在不同的包中,以区分不同的数据源。

  • 主数据源Mapper接口

    bash 复制代码
    package com.example.mapper.primary;
    
    import com.example.model.User;
    import org.apache.ibatis.annotations.Mapper;
    import java.util.List;
    
    @Mapper
    public interface PrimaryUserMapper {
    	List<User> findAll();
    }
  • 次数据源Mapper接口

    bash 复制代码
    package com.example.mapper.secondary;
    
    import com.example.model.Product;
    import org.apache.ibatis.annotations.Mapper;
    import java.util.List;
    
    @Mapper
    public interface SecondaryProductMapper {
    	List<Product> findAll();
    }
  • 主数据源Mapper XML文件

    在src/main/resources/mapper/primary目录下创建UserMapper.xml:

    bash 复制代码
    <?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.mapper.primary.PrimaryUserMapper">
    	<select id="findAll" resultType="com.example.model.User">
        	SELECT * FROM user
    	</select>
    </mapper>
  • 次数据源Mapper XML文件

    在src/main/resources/mapper/secondary目录下创建ProductMapper.xml:

    bash 复制代码
    <?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.mapper.secondary.SecondaryProductMapper">
    	<select id="findAll" resultType="com.example.model.Product">
        	SELECT * FROM product
    	</select>
    </mapper>
2.6 使用多数据源

在Service中注入对应的Mapper接口,并使用@Transactional注解指定事务管理器。

bash 复制代码
import com.example.mapper.primary.PrimaryUserMapper;
import com.example.mapper.secondary.SecondaryProductMapper;
import com.example.model.User;
import com.example.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class MyService {

    @Autowired
    private PrimaryUserMapper primaryUserMapper;

    @Autowired
    private SecondaryProductMapper secondaryProductMapper;

    @Transactional("primaryTransactionManager")
    public List<User> getUsers() {
        return primaryUserMapper.findAll();
    }

    @Transactional("secondaryTransactionManager")
    public List<Product> getProducts() {
        return secondaryProductMapper.findAll();
    }
}

三、动态数据源切换

如果需要动态切换数据源,可以通过以下步骤:

  1. 动态数据源配置

    bash 复制代码
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    public class DynamicDataSource extends AbstractRoutingDataSource {
    
    	@Override
    	protected Object determineCurrentLookupKey() {
        	return DataSourceContextHolder.getDataSourceType();
    	}
    }
  2. 数据源上下文持有者

    bash 复制代码
    public class DataSourceContextHolder {
    
    	private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
    	public static void setDataSourceType(String dataSourceType) {
        	contextHolder.set(dataSourceType);
    	}
    
    	public static String getDataSourceType() {
        	return contextHolder.get();
    	}
    
    	public static void clearDataSourceType() {
        	contextHolder.remove();
    	}
    }
  3. AOP切面

    bash 复制代码
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class DataSourceAspect {
    
    	@Before("@annotation(dataSource)")
    	public void switchDataSource(DataSource dataSource) {
        	DataSourceContextHolder.setDataSourceType(dataSource.value());
    	}
    }
  4. 自定义注解

    bash 复制代码
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
    	String value();
    }
  5. 使用动态数据源

    bash 复制代码
    @Service
    public class MyService {
    
    	@DataSource("primary")
    	public List<User> getUsers() {
        	return primaryUserMapper.findAll();
    	}
    
    	@DataSource("secondary")
    	public List<Product> getProducts() {
        	return secondaryProductMapper.findAll();
    	}
    }
相关推荐
绝无仅有3 分钟前
常用 Kubernetes (K8s) 命令指南
后端·面试·github
long31611 分钟前
代理设计模式
java·学习·程序人生·设计模式·代理模式
bobz96515 分钟前
ovs 桥接了 bond0.1234, 链路层功能还在,但 IP 层功能无法使用
后端
渣哥22 分钟前
惊呆!Java深拷贝 vs 浅拷贝,区别竟然这么大!
java
用户27079129381823 分钟前
为什么在 Java 中字符串是不可变的?
java
似水流年流不尽思念24 分钟前
Spring Bean有哪些生命周期回调方法?有哪几种实现方式?
后端·spring·面试
Moonbit31 分钟前
提交即有奖!MGPIC 游戏赛道官方推荐框架上线,直播同步解读赛题。 MoonBit MoonBit
后端·微信·程序员
郝同学的测开笔记31 分钟前
打通回家之路:OpenVPN,你的企业网络万能钥匙(一)
运维·后端·测试
whitepure31 分钟前
万字详解Java代码块
java·后端
忘带键盘了44 分钟前
Dish、DishVO 和 DishDTO
java