前言
在日常开发中,数据源可能不止一个。在多个数据源的情况下,就不能再使用springboot自动的配置了。需要手动整合多个。本篇主要讲下如何整合多个数据源。
准备工作
由于本地只安装了mysql,就用mysql的两个库做模拟。
建立springboot项目,引入的依赖如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.2-jre</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--lombok配置-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
<!-- Mysql驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies>
application.yml中需要做出配置:
server:
port: 26666
logging:
config: classpath:logback-spring.xml
spring:
datasource1:
jdbc-url: jdbc:mysql://127.0.0.1:3306/mybatisdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
datasource2:
jdbc-url: jdbc:mysql://127.0.0.1:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
事实上,每个数据源在springboot上下文中表现为一个datasource对象,或者类似druidDatasource这种。两个数据源的重点就是制作两个datasource使其存在于springboot上下文中。需要哪个用哪个即可。
jdbcTemplate方式
咱们将两个datasource的配置放在下面的两个配置类中
datasourceOne的配置:
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @author : wuwensheng
* @date : 11:00 2022/12/20
*/
@Configuration
@Slf4j
public class DataSourceOne {
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource1")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
/**
* 事务管理器
*/
@Bean(name = "transactionManager1")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
JdbcTemplate jdbcTemplateOne(@Qualifier("dataSource1") DataSource dataSource1) {
return new JdbcTemplate(dataSource1);
}
}
datasourceTwo的配置:
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @author : wuwensheng
* @date : 11:07 2022/12/20
*/
@Configuration
@Slf4j
public class DataSourceTwo {
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource2")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
/**
* 事务管理器
*/
@Bean(name = "transactionManager2")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource2") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
JdbcTemplate jdbcTemplateTwo(@Qualifier("dataSource2") DataSource dataSource1) {
return new JdbcTemplate(dataSource1);
}
}
配置做完之后主要测试的有两方面,一个是正常crud能否进行,另外一个是事务管理器能否生效。挨个做测试
测试jdbcTemplate的操作数据库能力
mybatiesdb这个库里面,选取account表做测试:
建立对应实体类:
test这个库里面,选取user表做测试:
建立对应实体类:
jdbcTemplate对驼峰映射规则自带支持。下面直接展开测试:
直接都做一个简单的查询,看能否成功:
测试第一个数据源,mybatiesdb这个库:
查询成功。
测试第二个数据源,test这个库:
没问题。
测试事务管理器能力,看能否成功支持事务
这里要稍微注意,由于咱们有多个事务管理器,因此在使用到@Transactional这个注解的时候,需要指明接下来要使用的事务管理器是哪一个,例如我用mybaties这个库做测试,相应的使用transcationManager1这个。
执行上面的程序,可以成功插入,如下图:
把程序修改下,故意制造异常,看事务能否回滚:
最终回滚成功,数据未插入数据库:
mybaties方式
使用mybaties方式做操作也是一样的,只不过配置需要稍稍改变。
增加@MapperScan注解,basePackages指明要扫描的接口,以便生成代理实现类。sqlSessionFactoryRef指明session会话工厂。
SqlSessionFactory里面要指定dao接口对应的xml文件的存放位置。
package com.cmdc.core.dbconfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @author : wuwensheng
* @date : 11:00 2022/12/20
*/
@Configuration
@MapperScan(basePackages = "com.cmdc.dao.datasourceone", sqlSessionFactoryRef = "sqlSessionFactory1")
@Slf4j
public class DataSourceOne {
private static final String MAPPER_LOCATION = "classpath*:mapper/*.xml";
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource1")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
/**
* 事务管理器
*/
@Bean(name = "transactionManager1")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
JdbcTemplate jdbcTemplateOne(@Qualifier("dataSource1") DataSource dataSource1) {
return new JdbcTemplate(dataSource1);
}
/**
* session工厂
*/
@Bean(name = "sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource1") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
}
此处注意一个细节,sqlSessionFactory里面我是指定了别名所在包,驼峰映射规则的,mybaties不会自动开启这些配置哦~
package com.cmdc.core.dbconfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @author : wuwensheng
* @date : 11:07 2022/12/20
*/
@Configuration
@MapperScan(basePackages = "com.cmdc.dao.datasourcetwo", sqlSessionFactoryRef = "sqlSessionFactory2")
@Slf4j
public class DataSourceTwo {
private static final String MAPPER_LOCATION = "classpath*:mapper2/*.xml";
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource2")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
/**
* 事务管理器
*/
@Bean(name = "transactionManager2")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource2") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
JdbcTemplate jdbcTemplateTwo(@Qualifier("dataSource2") DataSource dataSource1) {
return new JdbcTemplate(dataSource1);
}
/**
* session工厂
*/
@Bean(name = "sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource2") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
sessionFactoryBean.setTypeAliasesPackage("com.cmdc.entity");
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
sessionFactoryBean.setConfiguration(configuration);
return sessionFactoryBean.getObject();
}
}
两个对应的dao接口:
对应的sql xml文件:
接下来,继续测试下,能否成功查询
测试第一个数据源:
没问题。
测试第二个数据源:
也是可以的。
总结
还有许多整合过程中可能存在的问题,留给大家慢慢研究。例如整合druid连接池,其实一开始的application.yml的配置大概要变成下面这样:
## Spring DruidDataSource Configuration
# spring.datasource.druid.one.name=DataSource-1
# spring.datasource.druid.one.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.one.url=jdbc:mysql://localhost:3306/dbname1?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&noAccessToProcedureBodies=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
spring.datasource.druid.one.username=root
spring.datasource.druid.one.password=123456
spring.datasource.druid.one.initial-size=10
spring.datasource.druid.one.min-idle=10
spring.datasource.druid.one.max-active=100
spring.datasource.druid.one.max-wait=6000
spring.datasource.druid.one.test-while-idle=true
spring.datasource.druid.one.test-on-borrow=false
spring.datasource.druid.one.test-on-return=false
spring.datasource.druid.one.validation-query=select 1
spring.datasource.druid.one.time-between-eviction-runs-millis=30000
spring.datasource.druid.one.min-evictable-idle-time-millis=600000
spring.datasource.druid.one.max-evictable-idle-time-millis=900000
spring.datasource.druid.one.keep-alive=true
## Spring DruidDataSource Configuration
# spring.datasource.druid.two.name=DataSource-1
# spring.datasource.druid.two.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.two.url=jdbc:mysql://localhost:3306/dbname2?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&autoReconnect=true&autoReconnectForPools=true&noAccessToProcedureBodies=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
spring.datasource.druid.two.username=root
spring.datasource.druid.two.password=123456
spring.datasource.druid.two.initial-size=10
spring.datasource.druid.two.min-idle=10
spring.datasource.druid.two.max-active=100
spring.datasource.druid.two.max-wait=6000
spring.datasource.druid.two.test-while-idle=true
spring.datasource.druid.two.test-on-borrow=false
spring.datasource.druid.two.test-on-return=false
spring.datasource.druid.two.validation-query=select 1
spring.datasource.druid.two.time-between-eviction-runs-millis=30000
spring.datasource.druid.two.min-evictable-idle-time-millis=600000
spring.datasource.druid.two.max-evictable-idle-time-millis=900000
spring.datasource.druid.two.keep-alive=true
这些问题不再赘述,用到的时候慢慢研究。