首页 > 技术文章 > Spring Boot 初级入门教程(十九) —— 配置多数据源(附源码)

tzhuwb 2020-03-19 23:57 原文

距上篇文章《Spring Boot 初级入门教程(十八) —— 集成 MyBatis 另外一种开发方式》已跨了两个年头,时间确实有些久了!!!这么久没更新这个系列,一则由于自己确实忙了些,项目一个接一个的加班整;再者,由于项目需要,写了些其它方面的文章,比如关于 kafka、redis、IDEA 等。

另外,自己又买了个共享虚机,开通了尘封已久的博客,由于 .com 域名被别人抢注了,所以换成了 menglanglang.cn,如果有兴趣,也可以踩踩。

在开始这篇之前,首先要做的一件事,就是把原来的 demo 源码包中,包名换一下。因为原来一直是 com.menglanglang.xxx,现在换成 cn.menglanglang.xxx。如下:

   

另外,把代码中的原博客地址 http://blog.csdn.net/tzhuwb 换成 http://www.menglanglang.cn,纯属个人强迫症,理解。

言归正传,在项目开发中,我们常常会碰到一个服务配置多个数据源的情况,有些项目同时配置四五个 oracle,还配置着 mysql,gbase 等等。所以这篇主要说说如何简单的配置多数据源。

这里就简单以前面配置过的 oracle 和 mysql,同时配置到项目中,业务上同时操作不同的库来举例说明配置多数据源的步骤。

第一步,添加依赖包

在 pom 文件中,添加支持数据库连接池的 druid 依赖包,代码如下:

	<!-- 添加数据库连接池插件,支持多数据源 -->
	<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

 添加 Oracle 和 MySQL 依赖包,代码如下:

		<!-- mysql jdbc 插件 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>
		
		<!-- oracle jdbc 插件 -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc14</artifactId>
			<version>10.2.0.1.0</version>
		</dependency>

第二步,修改配置文件

在 application.properties 中,同时添加 oracle 数据库配置和 MySQL 数据库配置,分别用 first 和 second 来标识第一个和第二个数据源,代码如下所示:

#################################
## Oracle 数据库配置
#################################
# Oracle数据库连接
spring.datasource.first.url=jdbc:oracle:thin:@192.168.220.240:1521:orcl
# Oracle数据库用户名
spring.datasource.first.username=scott
# Oracle数据库密码
spring.datasource.first.password=123456
# Oracle数据库驱动(该配置可以不用配置,因为Spring Boot可以从url中为大多数数据库推断出它)
spring.datasource.first.driver-class-name=oracle.jdbc.OracleDriver

#################################
## MySQL 数据库配置
#################################
# MySQL数据库连接
spring.datasource.second.url=jdbc:mysql://192.168.220.240:3306/test_springboot?characterEncoding=UTF-8
# MySQL数据库用户名
spring.datasource.second.username=root
# MySQL数据库密码
spring.datasource.second.password=123456
# MySQL数据库驱动(该配置可以不用配置,因为Spring Boot可以从url中为大多数数据库推断出它)
spring.datasource.second.driver-class-name=com.mysql.jdbc.Driver

#################################
## 数据库共通配置
#################################
spring.datasource.max-active=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
#spring.datasource.validation-query=SELECT 1 FROM DUAL
#spring.datasource.test-on-borrow=true
#spring.datasource.test-while-idle=true
#spring.datasource.time-between-eviction-runs-millis=18800
#spring.datasource.jdbc-interceptors=ConnectionState;SlowQueryReport(threshold=10000)

第三步,添加配置类,分别读取数据库配置

创建一个包 config,分别创建 FirstDataSourceConfig 和 SecondDataSourceConfig,结构和代码如下:

FirstDataSourceConfig.java:

package cn.menglanglang.test.springboot.config;

import java.util.Objects;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
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.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class FirstDataSourceConfig {

	@Autowired
	private Environment environment;

	@Bean(name = "firstDataSource")
	@Primary
	public DataSource firstDataSource() {
		DruidDataSource dataSource = new DruidDataSource();
		String prefix = "spring.datasource";
		String prefixFirst = "spring.datasource.first";
		dataSource.setName("firstDataSource");
		dataSource.setDriverClassName(environment.getProperty(prefixFirst + ".driver-class-name").trim());
		dataSource.setUrl(environment.getProperty(prefixFirst + ".url").trim());
		dataSource.setUsername(environment.getProperty(prefixFirst + ".username").trim());
		dataSource.setPassword(environment.getProperty(prefixFirst + ".password").trim());
		dataSource.setMaxActive(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".max-active")).trim()));
		dataSource.setMinIdle(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".min-idle")).trim()));
		dataSource.setInitialSize(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".initial-size")).trim()));
		dataSource.setMaxWait(Long.parseUnsignedLong(Objects.requireNonNull(environment.getProperty(prefix + ".max-wait")).trim()));
//		dataSource.setTimeBetweenEvictionRunsMillis(Long.parseUnsignedLong(Objects.requireNonNull(environment.getProperty(prefix + ".time-between-eviction-runs-millis")).trim()));
//		dataSource.setMinEvictableIdleTimeMillis(Long.parseUnsignedLong(Objects.requireNonNull(environment.getProperty(prefix + ".min-evictable-idle-time-millis")).trim()));
//		dataSource.setTestWhileIdle(Boolean.parseBoolean(environment.getProperty(prefix + ".test-while-idle")));
//		dataSource.setTestOnBorrow(Boolean.parseBoolean(environment.getProperty(prefix + ".test-on-borrow")));
//		dataSource.setTestOnBorrow(Boolean.parseBoolean(environment.getProperty(prefix + ".test-on-return")));
//		dataSource.setTestOnBorrow(Boolean.parseBoolean(environment.getProperty(prefix + ".async-close-connection-enable")));
//		dataSource.setValidationQueryTimeout(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".validation-query-timeout")).trim()));
//		dataSource.setValidationQuery(environment.getProperty(prefix + ".validation-query"));
		return dataSource;
	}

	@Bean(name = "firstTransactionManager")
	@Primary
	public DataSourceTransactionManager firstTransactionManager() {
		return new DataSourceTransactionManager(firstDataSource());
	}

	@Bean(name = "firstSqlSessionFactory")
	@Primary
	public SqlSessionFactory firstSqlSessionFactory(@Qualifier("firstDataSource") DataSource dataSource) throws Exception {
		final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean() {
			{
				setDataSource(dataSource);
				setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/oracle/**/*.xml"));
			}
		};
		return sessionFactory.getObject();
	}

	@Bean(name = "firstSqlSessionTemplate")
	@Primary
	public SqlSessionTemplate firstSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

}

SecondDataSourceConfig.java:

package cn.menglanglang.test.springboot.config;

import java.util.Objects;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class SecondDataSourceConfig {

	@Autowired
	private Environment environment;

	@Bean(name = "secondDataSource")
	public DataSource secondDataSource() {
		DruidDataSource dataSource = new DruidDataSource();
		String prefix = "spring.datasource";
		String prefixSecond = "spring.datasource.second";
		dataSource.setName("secondDataSource");
		dataSource.setDriverClassName(environment.getProperty(prefixSecond + ".driver-class-name").trim());
		dataSource.setUrl(environment.getProperty(prefixSecond + ".url").trim());
		dataSource.setUsername(environment.getProperty(prefixSecond + ".username").trim());
		dataSource.setPassword(environment.getProperty(prefixSecond + ".password").trim());
		dataSource.setMaxActive(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".max-active")).trim()));
		dataSource.setMinIdle(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".min-idle")).trim()));
		dataSource.setInitialSize(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".initial-size")).trim()));
		dataSource.setMaxWait(Long.parseUnsignedLong(Objects.requireNonNull(environment.getProperty(prefix + ".max-wait")).trim()));
//		dataSource.setTimeBetweenEvictionRunsMillis(Long.parseUnsignedLong(Objects.requireNonNull(environment.getProperty(prefix + ".time-between-eviction-runs-millis")).trim()));
//		dataSource.setMinEvictableIdleTimeMillis(Long.parseUnsignedLong(Objects.requireNonNull(environment.getProperty(prefix + ".min-evictable-idle-time-millis")).trim()));
//		dataSource.setTestWhileIdle(Boolean.parseBoolean(environment.getProperty(prefix + ".test-while-idle")));
//		dataSource.setTestOnBorrow(Boolean.parseBoolean(environment.getProperty(prefix + ".test-on-borrow")));
//		dataSource.setTestOnBorrow(Boolean.parseBoolean(environment.getProperty(prefix + ".test-on-return")));
//		dataSource.setTestOnBorrow(Boolean.parseBoolean(environment.getProperty(prefix + ".async-close-connection-enable")));
//		dataSource.setValidationQueryTimeout(Integer.parseInt(Objects.requireNonNull(environment.getProperty(prefix + ".validation-query-timeout")).trim()));
//		dataSource.setValidationQuery(environment.getProperty(prefix + ".validation-query"));
		return dataSource;
	}

	@Bean(name = "secondTransactionManager")
	public DataSourceTransactionManager secondTransactionManager() {
		return new DataSourceTransactionManager(secondDataSource());
	}

	@Bean(name = "secondSqlSessionFactory")
	public SqlSessionFactory secondSqlSessionFactory(@Qualifier("secondDataSource") DataSource dataSource) throws Exception {
		final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean() {
			{
				setDataSource(dataSource);
				setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/mysql/**/*.xml"));
			}
		};
		return sessionFactory.getObject();
	}

	@Bean(name = "secondSqlSessionTemplate")
	public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}

}

第四步,扩展数据库接口查询封装类

接口查询封装类 DaoHelper.java,放在 common 包下,接口如下:

FirstDaoHelper.java:

package cn.menglanglang.test.springboot.common;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * @desc 数据库查询封装
 *
 * @see 封装了基本的查询方法,包括增删改查分页查等。 使用此类时,使用 @Resource 或 @Autowired 注解注入。
 * @author 孟郎郎
 * @blog http://www.menglanglang.cn
 * @version 1.0
 * @date 2020年3月19日下午11:02:03
 */
@Component
public class FirstDaoHelper {

	@Autowired
	@Qualifier("firstSqlSessionTemplate")
	private SqlSessionTemplate sqlSessionTemplate;

	public FirstDaoHelper(SqlSessionTemplate sqlSessionTemplate) {
		this.sqlSessionTemplate = sqlSessionTemplate;
	}

	/**
	 * 获取sqlsession factory
	 */
	public SqlSessionFactory getSqlSessionFactory() {
		return this.sqlSessionTemplate.getSqlSessionFactory();
	}

	// 这部分和前篇文章中的DaoHelper完全一样,在此省略。。。

}

SecondDaoHelper.java:

package cn.menglanglang.test.springboot.common;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * @desc 数据库查询封装
 *
 * @see 封装了基本的查询方法,包括增删改查分页查等。 使用此类时,使用 @Resource 或 @Autowired 注解注入。
 * @author 孟郎郎
 * @blog http://www.menglanglang.cn
 * @version 1.0
 * @date 2020年3月19日下午11:01:34
 */
@Component
public class SecondDaoHelper {

	@Autowired
	@Qualifier("secondSqlSessionTemplate")
	private SqlSessionTemplate sqlSessionTemplate;

	public SecondDaoHelper(SqlSessionTemplate sqlSessionTemplate) {
		this.sqlSessionTemplate = sqlSessionTemplate;
	}

	/**
	 * 获取sqlsession factory
	 */
	public SqlSessionFactory getSqlSessionFactory() {
		return this.sqlSessionTemplate.getSqlSessionFactory();
	}

	// 这部分和前篇文章中的DaoHelper完全一样,在此省略。。。

}

第五步,修改测试类、接口类、实现类及 xml 文件

这步骤相当于把《Spring Boot 初级入门教程(十五) —— 集成 MyBatis》和《Spring Boot 初级入门教程(十八) —— 集成 MyBatis 另外一种开发方式》中的代码合并一起,这里就不再详细贴了,可以直接下载打包好的源码查看即可。

分别为:MyBatisController3.java、MyBatisService3.java、MyBatisServiceImpl3.java、mybati-mapper3.xml。

第六步,测试

启动项目,分别访问以下路径,可以看到原来 Oracle 和 MySQL 的接口都可以拿到数据。

可以看到与原来的测试结果完全一致,所以可以同时使用两个数据源配置。

到此,多数据源简单配置步骤结束。^ ^

但一般项目中用到最多,也是最难的部分,是各个数据源参数的配置,以达到访问效率最优,需要结合服务部署机器资源配置,数据库配置,并发访问量等各个指标,来调节数据源参数。这里把常常用到的参数顺便罗列一下,便于服务调优查询。

yml 配置文件格式:

druid:
      access-to-underlying-connection-allowed: false #允许访问底层连接
      active-connection-stack-trace:  #活跃连接堆跟踪
      active-connections: #活跃连接列表
      aop-patterns: #AOP模式
      async-close-connection-enable: false #启用异步关闭连接
      async-init: false #异步初始化
      break-after-acquire-failure: false #失败后跳过
      clear-filters-enable: false #启动清除过滤器
      connect-properties:   #连接配置
      connection-error-retry-attempts: 1 #连接出错尝试几次重新连接
      connection-init-sqls:   #连接初始化语句
      connection-properties:  #连接属性
      create-scheduler:   #创建程序
      db-type:  #DB类型
      default-auto-commit: false #默认自动提交
      default-catalog:  #默认目录
      default-read-only: false #默认只读
      default-transaction-isolation: 1 #默认事务隔离
      destroy-scheduler:  #销毁程序
      driver:   #驱动类
      driver-class-name:  #驱动类名
      dup-close-log-enable: true #启用DUP关闭日志
      enable: true #启动连接池
      exception-sorter: 
      exception-sorter-class-name: 
      fail-fast: true #快速失败?
      filter-class-names: #过滤器类名称

properties 配置文件格式:

#连接池属性

spring.datasource.druid.initial-size=15
spring.datasource.druid.max-active=100
spring.datasource.druid.min-idle=15
spring.datasource.druid.max-wait=60000
spring.datasource.druid.time-between-eviction-runs-millis=60000
spring.datasource.druid.min-evictable-idle-time-millis=300000
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.validation-query=SELECT 1 FROM DUAL
spring.datasource.druid.validation-query-timeout=1000
spring.datasource.druid.keep-alive=true
spring.datasource.druid.remove-abandoned=true
spring.datasource.druid.remove-abandoned-timeout=180
spring.datasource.druid.log-abandoned=true
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
spring.datasource.druid.filters=stat,wall,slf4j
spring.datasource.druid.use-global-data-source-stat=true
spring.datasource.druid.preparedStatement=true
spring.datasource.druid.maxOpenPreparedStatements=100
spring.datasource.druid.connect-properties.mergeSql=true
spring.datasource.druid.connect-properties.slowSqlMillis=5000

属性说明:

源码下载:https://pan.baidu.com/s/1jA_EDzoUeXhgNA-e7wsnYA (提取码:4i84)

推荐阅读