目录
前言
正文
1.Druid 介绍和使用
2.其他多数据源解决方案
总结
前言
对于复杂的业务和项目,可能在一个单体项目中存在需要连接多个数据库的情况。这时,就会使用到多数据源,实际中遇到的可能性比较大。
正文
如果一个项目中需要连接 db1 ,同时一部分业务需要获取 db2 中的数据,则需要连接第二个数据源。这里一个数据源对应一个数据库,这两个数据库可能是部署在同一台服务器上,也可能不在同一台服务器上,通过配置 jdbc-url 来区别。
在配置上需要配置不同的 datasource 才能实现不同的数据源连接。
1.Druid 介绍和使用
Druid 是阿里旗下开源的数据库连接池,提供强大的监控和扩展功能,包括数据库性能健康,获取 SQL 日志的能力。除此之外,也可以和 MyBaties 配合用于多数据源搭建。
pom.xml依赖如下
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
为了测试多数据源,创建一个主库 test_spring_master,一个从库 test_spring_slave,创建 SQL 如下:
create database test_spring_master default character set utf8;
create database test_spring_slave default character set utf8;
配置文件内容如下:
spring:
datasource:
master: #主数据元
username: master
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_spring_master?useTimezone=true&serverTimezone=UTC
initialSize: 5
minIdle: 5
maxActive: 20
slave: #第二个数据源
username: slave
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test_spring_slave?useTimezone=true&serverTimezone=UTC
initialSize: 5
minIdle: 5
maxActive: 20
针对两个数据源配置,需要编写两个配置类,一个是主数据源配置类,另一个是从数据源配置类,分别命名为 MasterDataSourceConfig 和 SlaveDataSourceConfig,代码如下:
package org.example.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.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = MasterDataSourceConfig.PACKAGE_NAME, sqlSessionFactoryRef = "masterSqlSessionFactory")
public class MasterDataSourceConfig {
//定位到包类路径
static final String PACKAGE_NAME = "org.example.mapper.master";
//设置 mapper.xml位置。
//不存在 *.xml 这种模糊匹配,必须准确的名称
static final String MAPPER_LOCATION="classpath:org/example/mapper/master/MySinger.xml";
@Value("${spring.datasource.master.jdbc-url}")
private String url;
@Value("${spring.datasource.master.username}")
private String user;
@Value("${spring.datasource.master.password}")
private String password;
@Value("${spring.datasource.master.driver-class-name}")
private String driverClass;
/**
* 获得主数据源
* @return
*/
@Bean(name = "masterDataSource")
public DataSource masterDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClass);//设置驱动
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
/**
* 将 masterDataSource 注入到 masterTransactionManger
* @return 管理数据库实物
*/
@Bean(name = "masterTransactionManger")
public DataSourceTransactionManager masterTransactionManger(){
//使用了 @Bean,则可以直接依赖注入进去
return new DataSourceTransactionManager(masterDataSource());
}
@Bean(name = "masterSqlSessionFactory")
@Primary //如果有多个相同类型的Bean 优先使用本Bean
public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource masterDataSource){
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(masterDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResource(MasterDataSourceConfig.MAPPER_LOCATION));
try {
return sessionFactory.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
补充:
@MapperScan
注解用于指定需要扫描的 Mapper 接口所在的包名,它通常与@Configuration
注解一起使用,表示该注解所标注的类是一个配置类。basePackages
属性用于指定需要扫描的包名,多个包名可以用逗号隔开。例如,@MapperScan(basePackages = "com.example.mapper")
表示需要扫描com.example.mapper
包以及其子包中的 Mapper 接口。- 此外,
@MapperScan
注解还有一个sqlSessionFactoryRef
属性,用于指定使用的 SqlSessionFactory 的名称,即在 Spring IoC 容器中定义的 SqlSessionFactory Bean 的名称。在多个数据源的情况下,可以通过该属性为不同的 Mapper 接口指定不同的 SqlSessionFactory,以使用不同的数据源。- 也就是说,
@MapperScan
注解将指定包下的 MyBatis Mapper 接口与SqlSessionFactory
关联起来。为了让@MapperScan
注解知道要关联哪个SqlSessionFactory
实例,需要通过sqlSessionFactoryRef
属性指定SqlSessionFactory
的 Bean 名称。
可以看到 masterSqlSessionFactory 方法上有 @Primary 注解,说明这时主库。而从数据源配置类和主数据源配置类比较类似,唯一不同就是一些参数和注解不同。
package org.example.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.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = SlaveDataSourceConfig.PACKAGE_NAME, sqlSessionFactoryRef = "slaveSqlSessionFactory")
public class SlaveDataSourceConfig {
//定位到包类路径
static final String PACKAGE_NAME = "org.example.mapper.slave";
//设置 mapper.xml位置。
static final String MAPPER_LOCATION="classpath:org/example/mapper/slave/MyStore.xml";
@Value("${spring.datasource.slave.jdbc-url}")
private String url;
@Value("${spring.datasource.slave.username}")
private String user;
@Value("${spring.datasource.slave.password}")
private String password;
@Value("${spring.datasource.slave.driver-class-name}")
private String driverClass;
/**
* 获得主数据源
* @return
*/
@Bean(name = "slaveDataSource")
public DataSource slaveDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClass);//设置驱动
dataSource.setUrl(url);
dataSource.setUsername(user);
dataSource.setPassword(password);
return dataSource;
}
/**
* 将 masterDataSource 注入到 masterTransactionManger
* @return 管理数据库实物
*/
@Bean(name = "slaveTransactionManager")
public DataSourceTransactionManager slaveTransactionManager(){
//使用了 @Bean,则可以直接依赖注入进去
return new DataSourceTransactionManager(slaveDataSource());
}
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource slaveDataSource) throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(slaveDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResource(SlaveDataSourceConfig.MAPPER_LOCATION));
return sessionFactory.getObject();
}
}
为了进一步实验,需要在两个库中分别创建表,于是主库创建 m_singers 歌手表,在从数据库中创建 m_stores 商店表。
use test_spring_master;
create table `m_singers`(
`id` int(11) unsigned not null auto_increment comment '歌手主键',
`name` varchar(80) not null comment '歌手名',
`age` int(3) default null comment '年龄',
primary key (`id`)
)engine InnoDB default character set=utf8;
use test_spring_slave;
create table `m_stores`(
`id` int(11) unsigned not null auto_increment comment '主键',
`name` varchar(100) NOT NULL comment '店名',
`space` int(6) not null comment '单位平方米',
`description` varchar(300) not null comment '简介',
primary key (`id`)
)engine InnoDB DEFAULT character set=utf8;
创建用户并授予权限
create user 'master'@'localhost' identified by'123456';
show grants for 'master'@'localhost';
create user 'slave'@'localhost' identified by'123456';
GRANT ALL PRIVILEGES ON test_spring_slave.m_stores TO 'slave'@'localhost';
GRANT ALL PRIVILEGES ON test_spring_master.m_singers TO 'master'@'localhost';
继续分别创建对应的实体类
MyStore.java
package org.example.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class MyStore implements Serializable {
// 主键
private Integer id;
// 名称
private String name;
// 地区
private int space;
// 描述
private String description;
}
MySinger.java
package org.example.entity;
import lombok.Data;
import java.io.Serializable;
@Data
public class MySinger implements Serializable {
private Integer id;
private String name;
private int age;
}
对应的 Dao 文件:
MyStore。
package org.example.mapper.slave;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.example.entity.MyStore;
@Mapper
public interface MyStoreDao {
int insert(MyStore record);
int updateByPrimaryKey(MyStore record);
MyStore findByName(@Param("name") String name);
}
MySinger。
package org.example.mapper.master;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.example.entity.MySinger;
@Mapper
public interface MySingerDao {
int deleteByPrimaryKey(Integer id);
int insert(MySinger record);
int insertSelective(MySinger record);
MySinger selectByPrimaryKey(Integer id);
int updateByPrimaryKeySelective(MySinger record);
int updateByPrimaryKey(MySinger record);
MySinger findByName(@Param("name") String name);
}
剩下的 controller,service 部分基本就是一般流程了,就不继续向下写了。 最后自己进行测试:
完成 。
2.其他多数据源解决方案
其他解决方案也很多,如使用多个 Bean、自实现动态 DataSource 等,其中有一种方法非常简单高效 ,就是使用第三方的依赖包,动态加载不同的数据源,推荐使用 dynamic-datasource-spring-boot-starter。
dynamic-datasource-spring-boot-starter 是国内开发者维护的多数据源解决方案插件被作者称为一个基于 springboot 的快速继承多数据源的启动器。它的优点主要有支持数据源分组,适用于多种场景,纯粹多库、读写分离、一主多从混合模式。
POM.xml依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
数据源配置
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据数组,默认只即为 master
strict: false #设置严格模式,默认false不启动。启动后在未匹配到指定数据源时会抛出异常,不启动则使用默认数据源。
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver #3.2.0开始支持 SPI 可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave_2:
url: ENC(xxxxx) #内置加密,使用详情请看文档。
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.cj.jdbc.Driver
scherma: db/schema.sql #配置生效,自动初始化表格
data: db/data.sql #配置生效,自动初始化数据
continue-on-error: true #默认true ,初始化失败是否
separator: "," #sql 默认分隔符
#这些配置会配置一个默认库 master, 一个组 slave 下有两个子库 slave_1, slave_2
配置解释:
在这段配置中:
spring.datasource.dynamic
是dynamic-datasource-spring-boot-starter
的配置前缀。
primary: master
设置主数据源为 “master”。
strict: false
为非严格模式。如果请求的数据源不存在,系统会自动回退到主数据源,而不是抛出异常。
datasource:
下面列出了所有的数据源,每一个数据源都有一个名字(在这个例子中是 master, slave_1, slave_2),和对应的数据源详情(url, username, password, driver-class-name)。每个具体的数据源(比如 “master”)下:
url:
数据库的 JDBC URL。
username:
数据库用户名。
password:
数据库密码。ENC(xxx) 是一个加密格式,能够保护你的敏感信息不被明文展示。
driver-class-name:
JDBC 驱动类型。如果你依赖的版本在3.2.0以上,驱动可以被自动识别,这个配置也可以省略。
scherma: db/schema.sql
和data: db/data.sql
是你自定义的SQL语句文件。在 Spring Boot 中可以通过这种方式定义 SQL 文件的路径,然后在应用启动的时候自动执行这些 SQL 文件。强调一下,这只是个示例,你需要根据具体的数据库信息(如类型、地址、端口、用户名、密码等)去做相应的调整和修改。例如,替换
xx.xx.xx.xx
为实际的数据库服务器地址,并且替换username
和password
为正确的数据库访问凭证等。
最后使用 @DS 注解,采取就近原则,方法上的注解优先于类上的注解,代码如下 :
package org.example.service;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@DS("slave")
public class UserServiceImpl implements UserService{
@Autowired
private JdbcTemplate jdbcTemplate;
public List selectAll(){
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List selectByCondition(){
return jdbcTemplate.queryForList("select * from user where age > 10");
}
}
总结
多数据源搭建提供了灵活性、性能优化、隔离性和安全性方面的优势。它使应用程序能够更好地适应不同的需求,并在处理数据时提供更好的性能和可扩展性。