【SpringBoot】动态调用双数据源(使用参数实现,解决同名Bean问题)

本文介绍了一种在SpringBoot+Mybatis项目中实现动态多数据源切换的方法,以解决单一Oracle数据库无法承载大量数据的问题。通过自定义配置类、切面及注解,实现了根据业务参数动态选择不同数据源的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目前进行公司的一个BOSS项目的开发工作,由于单台Oracle数据库无法满足项目庞大的数据量,数据库开发不想进行去O,也就无法使用MySQL集群解决数据量陡增的问题。数据库开发提出了一个分库的方案,将整个Oracle按数据量分为两个库,同时在一个库的时候各个省份的表也是分离的,如果使用扫描就导致了在Java代码(SpringBoot + Mybatis)里存在同名的Bean,通过目录区分,这样Mapper也会是双份,代码冗余严重,所以根据网上提供的注解实现动态数据源调用的方式实现了根据入参调用不同数据源的方法,Demo(使用MySQL做)如下:

1.创建数据库

两张表结构非常简单,如下图。
a
b
2.配置application.yml

#Tomcat配置
server:
  port: 8080

#Actuator监控配置
security:
  user:
    name: asiainfo
    password: "!QAZ4esz"
    role: ACTUATOR
management:
    security:
      enabled: false
      roles: ACTUATOR
endpoints:
    jmx:
        enabled: false

#数据源配置
oracle:
  druid:
      stat_username: asiainfo
      stat_password: "!QAZ4esz"
  wlw_a:
    datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      url:  jdbc:mysql://127.0.0.1:3306/wlw_a?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
      username: root
      password: root
      password_encrypted: false
      driverClassName: com.mysql.jdbc.Driver
      maxActive: 200
      minIdle: 5
      maxWait: 60000
      poolPreparedStatements : true
      testOnBorrow : false
      testOnReturn : false
      removeAbandoned : false
      maxPoolPreparedStatementPerConnectionSize : 100
      keepAlive : true
      minEvictableIdleTimeMillis : 300000
      validationQuery : "SELECT 'x' FROM DUAL"
      testWhileIdle : false
      filters : "stat"

  wlw_b:
    datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      url:  jdbc:mysql://127.0.0.1:3306/wlw_b?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
      username: root
      password: root
      password_encrypted: false
      driverClassName: com.mysql.jdbc.Driver
      maxActive: 200
      minIdle: 5
      maxWait: 60000
      poolPreparedStatements : true
      testOnBorrow : false
      testOnReturn : false
      removeAbandoned : false
      maxPoolPreparedStatementPerConnectionSize : 100
      keepAlive : true
      minEvictableIdleTimeMillis : 300000
      validationQuery : "SELECT 'x' FROM DUAL"
      testWhileIdle : false
      filters : "stat"

3.Mapper

People.java

package com.asiainfo.test.entity;
public class People {

    private int id;

    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public People(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public People() {
    }

    @Override
    public String toString() {
        return "People{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

PeopleMapper.java

package com.asiainfo.test.mapper;

import com.asiainfo.test.entity.People;
import org.apache.ibatis.annotations.Mapper;

/**
 * Title PeopleMapper
 * Package com.asiainfo.test.mapper
 * Description people mapper
 * Author 唐磊
 * Date 2018-07-20 10:25
 */
@Mapper
public interface PeopleMapper {

    public People selectPeopleById(int id);

}

PeopleMapper.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.asiainfo.test.mapper.PeopleMapper" >
    <resultMap id="BaseResultMap" type="com.asiainfo.test.entity.People" >
        <id column="ID" property="id" jdbcType="INTEGER" />
        <result column="NAME" property="name" jdbcType="VARCHAR" />
    </resultMap>
    <sql id="Base_Column_List" >
        ID, NAME
    </sql>

    <select id="selectPeopleById" parameterType="int" statementType="PREPARED" resultMap="BaseResultMap">
        SELECT
        <include refid="Base_Column_List" />
        FROM people WHERE ID = #{id}
    </select>
</mapper>

4.DataSource配置

数据库配置由这四个类实现。
配置
DataSourceHolder :获取数据源、设置数据源等操作由此类实现。

package com.asiainfo.test.config;

import java.util.ArrayList;
import java.util.List;

public class DataSourceHolder {
    //线程本地环境
    private static final ThreadLocal<String> contextHolders = new ThreadLocal<String>();
    //数据源列表
    public static List<String> dataSourceIds = new ArrayList<>();
    //设置数据源
    public static void setDataSource(String customerType) {
        contextHolders.set(customerType);
    }
    //获取数据源
    public static String getDataSource() {
        return (String) contextHolders.get();
    }
    //清除数据源
    public static void clearDataSource() {
        contextHolders.remove();
    }
    //判断指定DataSrouce当前是否存在
    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}

DynamicDataSource :继承AbstractRoutingDataSource通过DataSourceHolder获取数据源。

package com.asiainfo.test.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }
}

DynamicDataSourceConfig :获取数据库配置并配置两个数据库,设置数据库SessionFactory以及事务管理TransactionManager。

package com.asiainfo.test.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

@Configuration
@MapperScan(basePackages = DynamicDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "wlwSqlSessionFactory")
public class DynamicDataSourceConfig implements EnvironmentAware{

    // 默认数据源
    private DataSource defaultDataSource;

    //第二数据源
    private DataSource secondDataSource;

    static final String PACKAGE = "com.asiainfo.test.mapper";

    private static final String MAPPER_LOCATION = "classpath:mapper/*.xml";

    private static final String DEFAULT_ENV_PATH = "oracle.wlw_a.datasource.";

    private static final String SECOND_ENV_PATH = "oracle.wlw_b.datasource.";

    private static final String DEFAULT_DATABASE = "DSA";

    private static final String SECOND_DATABASE = "DSB";

    @Override
    public void setEnvironment(Environment environment) {
        try {
            initDefaultDatasource(environment);
        } catch (Exception e) {
           System.out.println("创建主数据库失败,原因:" + e);
        }

        try {
            initOtherDatasource(environment);
        } catch (Exception e) {
            System.out.println("创建第二数据库失败,原因:" + e);
        }
    }

    private void initDefaultDatasource(Environment environment) throws Exception {
        // 读取主数据源
        defaultDataSource = buildDruidDataSource(environment,DEFAULT_ENV_PATH);
        DataSourceHolder.dataSourceIds.add(DEFAULT_DATABASE);
    }

    private void initOtherDatasource(Environment environment) throws Exception {
        // 读取第二数据源
        secondDataSource = buildDruidDataSource(environment,SECOND_ENV_PATH);
        DataSourceHolder.dataSourceIds.add(SECOND_DATABASE);
    }

    //拼装数据源
    private DruidDataSource buildDruidDataSource(Environment environment, String envPath) throws Exception {

        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, envPath);

        DruidDataSource dataSource = new DruidDataSource();

        dataSource.setDriverClassName(propertyResolver.getProperty("driverClass"));
        dataSource.setUrl(propertyResolver.getProperty("url"));
        dataSource.setUsername(propertyResolver.getProperty("username"));
        dataSource.setPassword(propertyResolver.getProperty("password"));
        dataSource.setMaxActive(Integer.parseInt(propertyResolver.getProperty("maxActive")));
        dataSource.setMinIdle(Integer.parseInt(propertyResolver.getProperty("minIdle")));
        dataSource.setMaxWait(Long.parseLong(propertyResolver.getProperty("maxWait")));
        dataSource.setPoolPreparedStatements(Boolean.parseBoolean(propertyResolver.getProperty("poolPreparedStatements")));
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(
                Integer.parseInt(propertyResolver.getProperty("maxPoolPreparedStatementPerConnectionSize")));
        dataSource.setKeepAlive(Boolean.parseBoolean(propertyResolver.getProperty("keepAlive")));
        dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(propertyResolver.getProperty("minEvictableIdleTimeMillis")));
        dataSource.setValidationQuery(propertyResolver.getProperty("validationQuery"));
        dataSource.setTestWhileIdle(Boolean.parseBoolean(propertyResolver.getProperty("testWhileIdle")));
        dataSource.setFilters(propertyResolver.getProperty("filters"));

        return dataSource;
    }

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

        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);

        // 配置多数据源
        Map<Object, Object> dbMap = new HashMap<Object, Object>();

        dbMap.put(DEFAULT_DATABASE,defaultDataSource);
        dbMap.put(SECOND_DATABASE,secondDataSource);

        dynamicDataSource.setTargetDataSources(dbMap);

        return dynamicDataSource;
    }

    @Primary
    @Bean(name = "wlwTransactionManager")
    public DataSourceTransactionManager wlwTransactionManager() throws SQLException {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource());
        transactionManager.setNestedTransactionAllowed(true);
        transactionManager.setRollbackOnCommitFailure(true);
        return transactionManager;
    }

    @Primary
    @Bean(name = "wlwSqlSessionFactory")
    public SqlSessionFactory wlwSqlSessionFactory(DataSource dynamicDataSource)
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(dynamicDataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(MAPPER_LOCATION));
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setUseGeneratedKeys(true);
        configuration.setUseColumnLabel(true);
        configuration.setJdbcTypeForNull(null);
        sessionFactory.setConfiguration(configuration);
        return sessionFactory.getObject();
    }
}

DynamicDataSourceAspect :设置切面在Service层,每当执行service下的方法是获取到所有参数,提前定义两个数据源的常量,根据相应参数切换不同的数据源。
优点:根据传来的参数动态切换数据源,减少代码冗余。
缺点:入参必须带数据库参数,并且要尽量放在前面,减少循环。

package com.asiainfo.test.config;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;


@Aspect
@Order(1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {

    @Around("execution(* com.asiainfo.test.service..*(..))")
    public Object switchDS(ProceedingJoinPoint point) throws Throwable {
        Class<?> className = point.getTarget().getClass();

        String dataSource = "DSA";//默认数据源

        Object[] args = point.getArgs();

		//遍历参数列表,判断调用的数据源
        for (Object arg : args) {
            if("wlw_b".equals(arg.toString())){
                dataSource = "DSB";
                break;
            }
        }

        // 切换数据源
        DataSourceHolder.setDataSource(dataSource);

        try {
            return point.proceed();
        } finally {
            DataSourceHolder.clearDataSource();
        }
    }
}

5.启动类与Controller

由于该Demo要做展示(有部分与会人员不懂代码),所以没有用单元测试,单独写了一个Controller用作展示,更为直观。

package com.asiainfo.test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication

@EnableAutoConfiguration
@ComponentScan("com.asiainfo.test.*")
@MapperScan("com.asiainfo.test.mapper")
public class DynmultipleApplication {

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

package com.asiainfo.test.controller;

import com.asiainfo.test.entity.People;
import com.asiainfo.test.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private TestService testService;

    @RequestMapping("people")
    public void test(HttpServletRequest request, HttpServletResponse response) throws Exception {

        String id = request.getParameter("id");
        String library = request.getParameter("library");

        People people = new People();

        people = testService.selectPeopleById(Integer.valueOf(id),library);

        response.setCharacterEncoding("UTF-8");

        PrintWriter out = response.getWriter();

        out.println(people.toString());

        out.flush();
        out.close();
    }
}

6.效果展示

根据参数library判断使用哪个数据源。
a
b
在这里插入图片描述

### 若依框架数据源配置教程 #### 1. 修改 `application.yml` 文件中的数据源配置 为了实现数据源的支持,在 `application.yml` 中需要定义两个独立的数据源,分别为主要 (`primary`) 和次要 (`secondary`) 数据源。具体配置如下所示: ```yaml spring: datasource: primary: url: jdbc:mysql://localhost:3306/primary_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver secondary: url: jdbc:mysql://localhost:3306/secondary_db?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.DataSource ``` 上述配置指定了两个MySQL数据库实例作为应用的主要和次级数据源[^1]。 #### 2. 创建自定义数据源配置类 创建一个新的Java类用于注册这两个DataSource Bean对象至Spring容器内,并指定其中一个为主数据源。这可以通过继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法来完成动态切换逻辑。 ```java @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(); } } ``` 此部分代码展示了如何通过 `@ConfigurationProperties` 注解读取配置文件中对应前缀下的属性值来构建实际使用的DataSource实例。 #### 3. 动态数据源路由器 为了让应用程序能够在运行期间根据业务需求自动选择合适的数据源执行SQL查询操作,则需引入DynamicDataSourceRouter组件负责管理多个数据源之间的切换工作。 ```java @Component public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public void setDefaultTargetDataSource(DataSource defaultTargetDataSource) { super.setDefaultTargetDataSource(defaultTargetDataSource); } public void setTargetDataSources(Map<Object, Object> targetDataSources) { super.setTargetDataSources(targetDataSources); afterPropertiesSet(); } protected Object determineCurrentLookupKey() { String dataSourceName = getDataSource(); if (dataSourceName == null || "".equals(dataSourceName.trim())) { return DataSourceEnum.PRIMARY.name(); } else { return dataSourceName; } } /** * 设置当前线程绑定的数据源名称 */ public static void setDataSource(String dataSourceType){ CONTEXT_HOLDER.set(dataSourceType); } /** * 获取当前线程绑定的数据源名称 */ public static String getDataSource(){ return CONTEXT_HOLDER.get(); } /** * 清除当前线程绑定的数据源名称 */ public static void clearDataSource(){ CONTEXT_HOLDER.remove(); } } ``` 这段程序实现了基于ThreadLocal机制存储当前请求所关联的目标数据源标识符的功能,从而允许开发者灵活控制每次JDBC调用的具体目标位置。 #### 4. MyBatis 配置调整 最后一步是在MyBatis的相关XML映射文件里加入必要的命名空间声明以及确保Mapper接口能够正确识别来自不同Schema结构内的实体模型。通常情况下只需保证各名唯一即可满足跨库联查的需求;但如果涉及到同名情况则建议采用全限定路径形式加以区别对待。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值