spring jpa 根据url入参动态切换数据源配置,全代码,附git地址

75 阅读2分钟

代码完整,可以正常启动,功能也已经实现,已经用于生产

但不敢保证百分百文章内容正确,如果存在错误之处欢迎各位大佬指出

git地址,可以直接clone启动

gitee.com/alamhubb/sp…

业务介绍

业务需要,需要根据业务判断访问不同的数据源,问了gpt也参考了github上的一些例子,gpt给的例子一开始有问题,github上的也不够简洁,融合了半天,搞出来了个最简单的版本,和大家分享下

核心逻辑

使用spring提供的AbstractRoutingDataSource实现动态数据源,根据入参动态设置 DataSourceContextHolder中的数据源名称

注意

因为使用了动态数据源,yml中的数据源默认配置不再生效,需要使用LocalContainerEntityManagerFactoryBean手动设置jpa相关配置

核心配置


@Configuration
public class DataSourceConfig {


    @Bean
    @ConfigurationProperties("app.datasource.main")
    public DataSource mainDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean
    @ConfigurationProperties("app.datasource.child")
    public DataSource childDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean
    public AbstractRoutingDataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();

        DataSource mainDataSource = mainDataSource();
        DataSource childDataSource = childDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("main", mainDataSource);
        targetDataSources.put("child", childDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(mainDataSource);
        return dynamicDataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource());
        em.setPackagesToScan("com.example.demo");
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        Map<String, Object> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        properties.put("hibernate.physical_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
        properties.put("hibernate.implicit_naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy");
        em.setJpaPropertyMap(properties);
        return em;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }
}
package com.example.demo.config;

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

其他全代码

pom.xml

<?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.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</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.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <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.springdoc</groupId>
            <artifactId>springdoc-openapi-ui</artifactId>
            <version>1.6.11</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </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>

application.properties

server.port=8090

#Datasource -main
app.datasource.main.jdbc-url=jdbc:mysql://localhost:3306/main?characterEncoding=utf-8&useSSL=false
app.datasource.main.username=root
app.datasource.main.password=root


#Datasource -child
app.datasource.child.jdbc-url=jdbc:mysql://localhost:3306/child?characterEncoding=utf-8&useSSL=false
app.datasource.child.username=root
app.datasource.child.password=root

uses.java

package com.example.demo.user;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String userName;

    // Getters and Setters
}

UserRepository.java

package com.example.demo.user;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Integer> {
    // 可以添加自定义查询方法
}

TestController.java

package com.example.demo.controller;

import com.example.demo.config.DataSourceContextHolder;
import com.example.demo.user.User;
import com.example.demo.user.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@Slf4j
public class TestController {
    @Autowired
    private UserRepository userRepository;

    //http://localhost:8090/test?type=main
    //http://localhost:8090/test?type=child
    @GetMapping("/test")
    public String getAllUsers(@RequestParam(name = "type") String type) {
        DataSourceContextHolder.setDataSourceType(type);
        User user = userRepository.findById(1).orElse(null);
        if (user == null) {
            log.error("is null, please add user");
            return "is null, please add user";
        }
        return user.getUserName();
    }
}