SpringBoot + Druid+ JPA 多数据源配置

本文详细介绍了如何在Spring Boot应用中配置多个数据源,包括引入Druid依赖,配置application.yaml文件,动态数据源和数据源初始化,以及自定义注解和切面实现方法级别的数据源切换。通过这种方法,可以灵活地在不同数据库间切换,满足多库操作需求。
摘要由CSDN通过智能技术生成

说明:以自定义注解切面方式(方法级),指定调用的数据源。

一、引入 druid 包

<!-- druid数据源 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

二、修改application.yaml 配置文件

# MySQL配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
            db-1:
                driver-class-name: com.mysql.cj.jdbc.Driver
                username: root
                password: 123456
                url: jdbc:mysql://localhost:3306/db_1?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
                initialSize: 5
                minIdle: 5
                maxActive: 20
                maxWait: 60000
                removeAbandoned: true
                removeAbandonedTimeout: 180
                timeBetweenEvictionRunsMillis: 60000
                minEvictableIdleTimeMillis: 300000
                validationQuery: SELECT1FROMDUAL
                testWhileIdle: true
                testOnBorrow: false
                testOnReturn: false
                poolPreparedStatements: true
                maxPoolPreparedStatementPerConnectionSize: 50
                filters: wall
                logSlowSql: true
            db-2:
                driver-class-name: com.mysql.cj.jdbc.Driver
                username: root
                password: 123456
                url: jdbc:mysql://localhost:3306/db_2?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
                initialSize: 5
                minIdle: 5
                maxActive: 20
                maxWait: 60000
                removeAbandoned: true
                removeAbandonedTimeout: 180
                timeBetweenEvictionRunsMillis: 60000
                minEvictableIdleTimeMillis: 300000
                validationQuery: SELECT1FROMDUAL
                testWhileIdle: true
                testOnBorrow: false
                testOnReturn: false
                poolPreparedStatements: true
                maxPoolPreparedStatementPerConnectionSize: 50
                filters: wall
                logSlowSql: true
    jpa:
        database: mysql
        show-sql: true
        # generate-ddl: true
        #设置数据库方言  记住必须要使用 MySQL5InnoDBDialect 指定数据库类型对应InnoDB  ;如果使用MySQLDialect 则对应的是MyISAM
        database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

注意:
1.数据库连接地址配置信息,key为url(默认数据库连接池Hikari的配置为jdbc-url )
2.数据源名称不能首字母大写,不能使用下划线;为了方便此处数据源名称和后边初始化数据源,以及注解中指定数据源,使用相同名字。

三、动态数据源和数据源初始化

多数据源配置原理:
将多个数据源信息,以map的形式保存,在数据库操作时用key获取数据源信息

定义一个类继承 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法,在方法内获取相应的数据源的key信息,获取数据源时,会使用此方法返回的key去map中查找对应的数据源。

package com.ylx.config.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.util.Map;

/**
 * 自定义动态数据源
 * date:2021-07-13
 * author:YCH
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    // 保存数据库连接连接配置信息
    private static final ThreadLocal<String> contextHolder = new ThreadLocal();

    public DynamicDataSource(DruidDataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = getDataSource();
        System.out.println("当前数据源为:" + dataSource);
        return dataSource;
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
        System.out.println("contextHolder.remove() result :=========>>>" + contextHolder.get());
    }

}

配置数据源信息

package com.ylx.config.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
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.stereotype.Component;

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


/**
 * 多数据源配置
 * date:2021-07-07
 * author:YCH
 */
@Configuration
@Component
@Slf4j
public class DynamicDataSourceConfig {

    @Autowired
    Environment env; // 从env中获取配置文件中配置信息

    /**
     * db-1 数据源配置信息 前缀
     */
    private final String dataApiPrefix = "spring.datasource.druid.db-1.";

    /**
     * db-2 数据源配置信息 前缀
     */
    private final String wordDBPrefix = "spring.datasource.druid.db-2.";


    /**
     * db-1 库数据源
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.db-1")
    public DruidDataSource dataApiDataSource() {
        System.out.println("[=================  db-1 build ===============]");
        // 手动创建连接池对象,配置连接池信息
        DruidDataSource dataSource = new DruidDataSource();

        // 数据库类型
        dataSource.setDbType(JdbcConstants.MYSQL);
        // 连接地址
        dataSource.setUrl(env.getProperty(dataApiPrefix + "url"));
        // 数据库连接用户名
        dataSource.setUsername(env.getProperty(dataApiPrefix + "username"));
        // 数据库连接密码
        dataSource.setPassword(env.getProperty(dataApiPrefix + "password"));
        // 驱动类
        dataSource.setDriverClassName(env.getProperty(dataApiPrefix + "driver-class-name"));
        // 定义初始连接数
        dataSource.setInitialSize(Integer.parseInt(env.getProperty(dataApiPrefix + "initialSize")));
        // 最小空闲
        dataSource.setMinIdle(Integer.parseInt(env.getProperty(dataApiPrefix + "minIdle")));
        // 定义最大连接数
        dataSource.setMaxActive(Integer.parseInt(env.getProperty(dataApiPrefix + "maxActive")));
        // 获取连接等待超时的时间
        dataSource.setMaxWait(Long.parseLong(env.getProperty(dataApiPrefix + "maxWait")));
        // 超过时间限制是否回收
        dataSource.setRemoveAbandoned(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "removeAbandoned")));
        // 超过时间限制多长
        dataSource.setRemoveAbandonedTimeout(Integer.parseInt(env.getProperty(dataApiPrefix + "removeAbandonedTimeout")));

        // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(env.getProperty(dataApiPrefix + "timeBetweenEvictionRunsMillis")));
        // 配置一个连接在池中最小生存的时间,单位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(env.getProperty(dataApiPrefix + "minEvictableIdleTimeMillis")));
        // 用来检测连接是否有效的sql,要求是一个查询语句
        dataSource.setValidationQuery(env.getProperty(dataApiPrefix + "validationQuery"));
        // 申请连接的时候检测
        dataSource.setTestWhileIdle(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "testWhileIdle")));
        // 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnBorrow(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "testOnBorrow")));
        // 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnReturn(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "testOnReturn")));
        // 打开PSCache,并且指定每个连接上PSCache的大小
        dataSource.setPoolPreparedStatements(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "poolPreparedStatements")));
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(Integer.parseInt(env.getProperty(dataApiPrefix + "maxPoolPreparedStatementPerConnectionSize")));
        // 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
        // 监控统计用的filter:stat
        // 日志用的filter:log4j
        // 防御SQL注入的filter:wall
        String filters = env.getProperty(dataApiPrefix + "filters");
        try {
            dataSource.setFilters(filters);
        } catch (SQLException e) {
            log.error("扩展插件失败.{}", e.getMessage());
        }

        return dataSource;
    }

    /**
     * db-2 库数据源
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.db-2")
    public DruidDataSource wordDbDataSource() {
        System.out.println("[================= db-2 built ===============]");

        // 手动创建连接池对象,配置连接池信息
        DruidDataSource dataSource = new DruidDataSource();

        // 数据库类型
        dataSource.setDbType(JdbcConstants.MYSQL);
        // 连接地址
        dataSource.setUrl(env.getProperty(wordDBPrefix + "url"));
        // 数据库连接用户名
        dataSource.setUsername(env.getProperty(wordDBPrefix + "username"));
        // 数据库连接密码
        dataSource.setPassword(env.getProperty(wordDBPrefix + "password"));
        // 驱动类
        dataSource.setDriverClassName(env.getProperty(wordDBPrefix + "driver-class-name"));
        // 定义初始连接数
        dataSource.setInitialSize(Integer.parseInt(env.getProperty(wordDBPrefix + "initialSize")));
        // 最小空闲
        dataSource.setMinIdle(Integer.parseInt(env.getProperty(wordDBPrefix + "minIdle")));
        // 定义最大连接数
        dataSource.setMaxActive(Integer.parseInt(env.getProperty(wordDBPrefix + "maxActive")));
        // 获取连接等待超时的时间
        dataSource.setMaxWait(Long.parseLong(env.getProperty(wordDBPrefix + "maxWait")));
        // 超过时间限制是否回收
        dataSource.setRemoveAbandoned(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "removeAbandoned")));
        // 超过时间限制多长
        dataSource.setRemoveAbandonedTimeout(Integer.parseInt(env.getProperty(wordDBPrefix + "removeAbandonedTimeout")));

        // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(env.getProperty(wordDBPrefix + "timeBetweenEvictionRunsMillis")));
        // 配置一个连接在池中最小生存的时间,单位是毫秒
        dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(env.getProperty(wordDBPrefix + "minEvictableIdleTimeMillis")));
        // 用来检测连接是否有效的sql,要求是一个查询语句
        dataSource.setValidationQuery(env.getProperty(wordDBPrefix + "validationQuery"));
        // 申请连接的时候检测
        dataSource.setTestWhileIdle(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "testWhileIdle")));
        // 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnBorrow(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "testOnBorrow")));
        // 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
        dataSource.setTestOnReturn(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "testOnReturn")));
        // 打开PSCache,并且指定每个连接上PSCache的大小
        dataSource.setPoolPreparedStatements(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "poolPreparedStatements")));
        dataSource.setMaxPoolPreparedStatementPerConnectionSize(Integer.parseInt(env.getProperty(wordDBPrefix + "maxPoolPreparedStatementPerConnectionSize")));
        // 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
        // 监控统计用的filter:stat
        // 日志用的filter:log4j
        // 防御SQL注入的filter:wall
        String filters = env.getProperty(wordDBPrefix + "filters");
        try {
            dataSource.setFilters(filters);
        } catch (SQLException e) {
            log.error("扩展插件失败.{}", e.getMessage());
        }

        return dataSource;
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DruidDataSource db1, DruidDataSource db2){
        Map<Object, Object> targetDataSources = new HashMap<>();

        targetDataSources.put("db-1",db1);
        targetDataSources.put("db-2", db2);
        return new DynamicDataSource(dataApiDataSource, targetDataSources);
    }

}


四、自定义注解

自定义注解,在方法上使用注解,以切面的形式指定数据源

package com.ylx.config.datasource;

import java.lang.annotation.*;

/**
 * 自定义注解,方法上添加 @DataSource(name = "DatasourceName") 指定这个方法内调用的数据源
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}

定义切面

package com.ylx.config.datasource;

import com.ylx.util.ConfigUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.lang.reflect.Method;

/**
 * 数据源切面
 * date:2021-07-13
 * author:YCH
 */
@Aspect
@Order(-1)
@Component
public class DataSourceAspect {

    @PersistenceContext
    private EntityManager entityManager;

    /**
     * 指定 持有 @Datasource 注解的触发
     */
    @Pointcut("@annotation(com.ylx.config.datasource.DataSource)")
    public void dataSourcePointCut() {

    }

    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();

        DataSource dataSource = method.getAnnotation(DataSource.class);
        if(dataSource == null){
            // 设置默认数据库
            DynamicDataSource.setDataSource("db-1");
        }else {
            DynamicDataSource.setDataSource(dataSource.name());
        }

        try {
            return point.proceed();
        } finally {
            // 清空当前数据库连接信息
            DynamicDataSource.clearDataSource();
            // 使用完之后断开连接,否则会一直使用同一连接(重要)
            SessionImplementor sessionImplementor = entityManager.unwrap(SessionImplementor.class);
            sessionImplementor.disconnect();
        }
    }

}

五、使用

本人是在service的实现类中方法是使用注解的。如果service方法中,不止使用一个数据源,则可以在dao层添加注解。

@Service
public class DataServiceImpl implements DataService{
	
	@Autowired
	UserRepository userRepository;
	
	@DataSource(name = "db-1")
	@Overwrite
	public Object getData(){
		return userRepository.findAll();
	}

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值