【JAVA秒会技术之随意切换数据库】Spring如何高效的配置多套数据源

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介:  Spring如何高效的配置多套数据源     真正的开发中,难免要使用多个数据库,进行不同的切换。无论是为了实现“读写分离”也好,还是为了使用不同的数据库(“MySQL”或“Oracle”或“SQLServer”)。传统的方法,是配置多套Spring配置文件与Mysql配置文件,不仅配置起来较为混乱,而且切换及对事物的管理,也很麻烦。下面,博主就介绍一种方法,帮助大家解决“Spring

 Spring如何高效的配置多套数据源

    真正的开发中,难免要使用多个数据库,进行不同的切换。无论是为了实现读写分离”也好,还是为了使用不同的数据库“MySQL”或“Oracle”或“SQLServer”)。传统的方法,是配置多套Spring配置文件与Mysql配置文件,不仅配置起来较为混乱,而且切换及对事物的管理,也很麻烦。下面,博主就介绍一种方法,帮助大家解决“Spring如何高效的配置多套数据源”!

(一)Spring核心配置文件

1.Spring-conf配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans.xsd 
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd 
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx.xsd 
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!-- 使用spring注解 -->
	<context:annotation-config />
	<!-- 扫描注解 -->
	<context:component-scan base-package="com.***.****">
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
	</context:component-scan>
	<!-- 配置文件读取 -->
	<bean id="configProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
		<property name="locations">
			<list>
				<value>classpath:*.properties</value>
			</list>
		</property>
		<property name="fileEncoding" value="UTF-8" />
	</bean>
	<!-- 通过@Value注解读取.properties配置内容 -->
	<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
		<property name="properties" ref="configProperties" />
	</bean>
	<!--=================== 多数据配置开始 =======================-->
	<!-- 数据源1-- druid数据库连接池 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
		<!-- 数据库基本信息配置 -->
		<property name="url" value="${jdbc.url}" />
		<property name="username" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="driverClassName" value="${jdbc.driverClassName}" />
		<property name="filters" value="${jdbc.filters}" />
		<!-- 最大并发连接数 -->
		<property name="maxActive" value="${jdbc.maxActive}" />
		<!-- 初始化连接数量 -->
		<property name="initialSize" value="${jdbc.initialSize}" />
		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="${jdbc.maxWait}" />
		<!-- 最小空闲连接数 -->
		<property name="minIdle" value="${jdbc.minIdle}" />
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
		<property name="validationQuery" value="${jdbc.validationQuery}" />
		<property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
		<property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
		<property name="testOnReturn" value="${jdbc.testOnReturn}" />
		<property name="maxOpenPreparedStatements" value="${jdbc.maxOpenPreparedStatements}" />
		<!-- 打开removeAbandoned功能 -->
		<property name="removeAbandoned" value="${jdbc.removeAbandoned}" />
		<!-- 1800秒,也就是30分钟 -->
		<property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}" />
		<!-- 关闭abanded连接时输出错误日志 -->
		<property name="logAbandoned" value="${jdbc.logAbandoned}" />
	</bean>
	<!-- 数据源2-- druid数据库连接池 -->
	<bean id="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
		<!-- 数据库基本信息配置 -->
		<property name="url" value="${jdbc2.url}" />
		<property name="username" value="${jdbc2.username}" />
		<property name="password" value="${jdbc2.password}" />
		<property name="driverClassName" value="${jdbc2.driverClassName}" />
		<property name="filters" value="${jdbc2.filters}" />
		<!-- 最大并发连接数 -->
		<property name="maxActive" value="${jdbc2.maxActive}" />
		<!-- 初始化连接数量 -->
		<property name="initialSize" value="${jdbc2.initialSize}" />
		<!-- 配置获取连接等待超时的时间 -->
		<property name="maxWait" value="${jdbc2.maxWait}" />
		<!-- 最小空闲连接数 -->
		<property name="minIdle" value="${jdbc2.minIdle}" />
		<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
		<property name="timeBetweenEvictionRunsMillis" value="${jdbc2.timeBetweenEvictionRunsMillis}" />
		<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
		<property name="minEvictableIdleTimeMillis" value="${jdbc2.minEvictableIdleTimeMillis}" />
		<property name="validationQuery" value="${jdbc2.validationQuery}" />
		<property name="testWhileIdle" value="${jdbc2.testWhileIdle}" />
		<property name="testOnBorrow" value="${jdbc2.testOnBorrow}" />
		<property name="testOnReturn" value="${jdbc2.testOnReturn}" />
		<property name="maxOpenPreparedStatements" value="${jdbc2.maxOpenPreparedStatements}" />
		<!-- 打开removeAbandoned功能 -->
		<property name="removeAbandoned" value="${jdbc2.removeAbandoned}" />
		<!-- 1800秒,也就是30分钟 -->
		<property name="removeAbandonedTimeout" value="${jdbc2.removeAbandonedTimeout}" />
		<!-- 关闭abanded连接时输出错误日志 -->
		<property name="logAbandoned" value="${jdbc2.logAbandoned}" />
	</bean>
	<!-- Spring多数据源-配置 -->
	<bean id="multipleDataSource" class="com.dshl.commons.utlis.MultipleDataSource">
		<property name="targetDataSources">
			<map>
	            <!-- 配置目标数据源 -->
				<entry value-ref="dataSource" key="dataSource" />
				<entry value-ref="dataSource2" key="dataSource2" />
			</map>
		</property>
        <!-- 配置默认使用的据源 -->
		<property name="defaultTargetDataSource" ref="dataSource" />
	</bean>
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注意:SqlSessionFactory的ref一定要指向multipleDataSource -->
		<property name="dataSource" ref="multipleDataSource" />
		<property name="configLocation" value="classpath:/mybatis/mybatis-config.xml" />
		<!-- mapper扫描 -->
		<property name="mapperLocations">
			<list>
				<value>classpath:/mybatis/mapper/*.xml</value>
			</list>
		</property>
	</bean>
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.*****.dao" />
		<property name="annotationClass" value="org.springframework.stereotype.Repository" />
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
	</bean>
	<!-- 多数据源-配置-结束 -->

	<!-- 配置 aspectj -->
	<aop:aspectj-autoproxy />
</beans>

2.配置jdbc.properties

#------------------------JDBC-------------------------------
jdbc.url:jdbc:sqlserver://ip地址1:端口;database=数据库;integratedSecurity=false
jdbc.driverClassName:com.microsoft.sqlserver.jdbc.SQLServerDriver
jdbc.username:用户名
jdbc.password:密码
jdbc.filters:stat
jdbc.maxActive:10
jdbc.initialSize:2
jdbc.maxWait:60000
jdbc.minIdle:2
jdbc.timeBetweenEvictionRunsMillis:60000
jdbc.minEvictableIdleTimeMillis:300000
jdbc.validationQuery:SELECT 'x' FROM DUAL
jdbc.testWhileIdle:true
jdbc.testOnBorrow:false
jdbc.testOnReturn:false
jdbc.maxOpenPreparedStatements:20
jdbc.removeAbandoned:true
jdbc.removeAbandonedTimeout:180
jdbc.logAbandoned:true
 
#---------------------------JDBC-2----------------------------
jdbc2.url:jdbc:mysql://ip地址1:端口/数据库?autoReconnect=true
jdbc2.driverClassName:com.mysql.jdbc.Driver
jdbc2.username:用户名
jdbc2.password:密码
jdbc2.filters:stat
jdbc2.maxActive:10
jdbc2.initialSize:2
jdbc2.maxWait:60000
jdbc2.minIdle:2
jdbc2.timeBetweenEvictionRunsMillis:60000
jdbc2.minEvictableIdleTimeMillis:300000
jdbc2.validationQuery:SELECT 'x' FROM DUAL
jdbc2.testWhileIdle:true
jdbc2.testOnBorrow:false
jdbc2.testOnReturn:false
jdbc2.maxOpenPreparedStatements:20
jdbc2.removeAbandoned:true
jdbc2.removeAbandonedTimeout:180
jdbc2.logAbandoned:true


(二)配置通知与切面

1.配置通知

package com.netease.numen.core.annotation;
import java.lang.annotation.*;
/**
 * @author liyan
 */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DatabaseConfiguration {
	/**
	 * annotation description
	 * @return {@link java.lang.String}
	 */
	String description() default "";

	/**
	 * annotation value ,default value "dataSource"
	 * @return {@link java.lang.String}
	 */
	String value() default "";
}

2.配置切面

package com.netease.numen.core.aop;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netease.numen.core.annotation.DatabaseConfiguration;
import com.netease.numen.core.util.MultipleDataSource;
/**
 * 数据库配置切面
 * @author liyan
 */
@Aspect
public class DatabaseConfigurationAspect {

	/**
	 * default dataSource
	 */
	public static final String DEFAULT_DATASOURCE = "dataSource";

	/**
	 * 日志
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseConfigurationAspect.class);

	@Pointcut("@annotation(com.netease.numen.core.annotation.DatabaseConfiguration)")
	public void DBAspect() {
	}

	/**
	 * 前置通知
	 * @param joinPoint 切点
	 */
	@Before("DBAspect()")
	public void doBefore(JoinPoint joinPoint) {
		try {
			MultipleDataSource.setDataSourceKey(getTargetDataSource(joinPoint));
			LOGGER.info("Methods Described:{}", getDescription(joinPoint));
			LOGGER.info("Replace DataSource:{}", getTargetDataSource(joinPoint));
		} catch (Exception e) {
			LOGGER.warn("DataSource Switch Exception:{}", e);
			MultipleDataSource.setDataSourceKey(DEFAULT_DATASOURCE);
		}
	}

	/**
	 * 异常通知
	 * @param joinPoint  切点
	 * @param e  异常
	 */
	@AfterThrowing(pointcut = "DBAspect()", throwing = "e")
	public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
		try {
			MultipleDataSource.setDataSourceKey(DEFAULT_DATASOURCE);
		} catch (Exception ex) {
			LOGGER.warn("DataSource Switch Exception:{}", e);
		}
	}

	/**
	 * 方法后通知
	 * @param joinPoint  切点
	 */
	@After("DBAspect()")
	public void doAfter(JoinPoint joinPoint) {
		try {
			MultipleDataSource.setDataSourceKey(DEFAULT_DATASOURCE);
			LOGGER.info("Restore Default DataSource:{}", DEFAULT_DATASOURCE);
		} catch (Exception e) {
			LOGGER.warn("Restore Default DataSource Exception:{}", e);
		}
	}

	/**
	 * 获取数据源描述
	 * @param joinPoint 切点
	 * @return DB-Key(数据库)
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	public static String getDescription(JoinPoint joinPoint) throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		String description = "";
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class[] clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length) {
					description = method.getAnnotation(DatabaseConfiguration.class).description();
					if (description == null || "".equals(description))
						description = "Database switch";
					break;
				}
			}
		}
		return description;
	}

	/**
	 * 获取数据源
	 * @param joinPoint 切点
	 * @return DB-Key(数据库)
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	public static String getTargetDataSource(JoinPoint joinPoint) throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		String value = "";
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class[] clazzs = method.getParameterTypes();
				if (clazzs.length == arguments.length) {
					value = method.getAnnotation(DatabaseConfiguration.class).value();
					if (value == null || "".equals(value))
						value = DEFAULT_DATASOURCE;
					break;
				}
			}
		}
		return value;
	}
}

(三)编写切换数据源工具类

package com.netease.numen.core.util;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 多数据源配置
 * 
 * 说明:定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要
 * 实现determineCurrentLookupKey方法即可 
 * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由
 * DynamicDataSourceHolder完成。
 * 
 * @author Liyan
 */
public class MultipleDataSource extends AbstractRoutingDataSource {

	private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();

	public static void setDataSourceKey(String dataSource) {
		dataSourceKey.set(dataSource);
	}
	@Override
	protected Object determineCurrentLookupKey() {
		// TODO Auto-generated method stub
		return dataSourceKey.get();
	}
}

(四)如何使用

这就很简单了,只要在serviceImpl中,要切换数据源前,调用工具类:

	public String isExist(String jobNumber) throws DataAccessException {
		try {
			//切换数据源,对中间库操作
			MultipleDataSource.setDataSourceKey("dataSource4");
			Map<String, Object> param = new HashMap<String, Object>(0);
			param.put("jobNumber", jobNumber);
			return mapper.isExist(param);
		} catch (DataAccessException e) {
			throw e;
		} finally{
			//切回数据源
			MultipleDataSource.setDataSourceKey("dataSource");
		}
	}









 

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4天前
|
XML Java 数据库连接
性能提升秘籍:如何高效使用Java连接池管理数据库连接
在Java应用中,数据库连接管理至关重要。随着访问量增加,频繁创建和关闭连接会影响性能。为此,Java连接池技术应运而生,如HikariCP。本文通过代码示例介绍如何引入HikariCP依赖、配置连接池参数及使用连接池高效管理数据库连接,提升系统性能。
27 5
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
14 2
|
15天前
|
人工智能 前端开发 Java
基于开源框架Spring AI Alibaba快速构建Java应用
本文旨在帮助开发者快速掌握并应用 Spring AI Alibaba,提升基于 Java 的大模型应用开发效率和安全性。
基于开源框架Spring AI Alibaba快速构建Java应用
|
16天前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
30 3
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
33 2
|
16天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
35 2
|
16天前
|
Java 数据库连接 数据库
深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能
在Java应用开发中,数据库操作常成为性能瓶颈。本文通过问题解答形式,深入探讨Java连接池技术如何通过复用数据库连接、减少连接建立和断开的开销,从而显著提升系统性能。文章介绍了连接池的优势、选择和使用方法,以及优化配置的技巧。
16 1
|
9天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
22 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9

热门文章

最新文章