Введение
В текущем проекте встречаются сценарии, в которых необходимо подключить несколько баз данных. В настоящее время необходимо ввести несколько источников данных.
Также существуют некоторые сценарии, такие как следующие:
- При подключении к третьей стороне некоторые партнеры не будут разрабатывать для вас функцию, отвечающую некоторым вашим потребностям. Они могут предоставить вам учетную запись только для чтения, которая может получить доступ к источнику данных. Какие данные вам нужно получить, зависит от вас. для выполнения логической обработки, то невозможно избежать необходимости использования нескольких источников данных.
- Бизнес-данные достигли порядка величины, а использование одной базы данных для хранения достигло узкого места. Для управления данными необходимы такие операции, как подбазы данных и таблицы. При работе с данными неизбежно задействованы несколько источников данных.
После поиска в Интернете я обнаружил, что многие примеры неверны, поэтому решил написать один, чтобы иметь возможность ссылаться на него, когда мне понадобится использовать его в будущем.
Если вы просто хотите увидеть код, прокрутите до конца, чтобы увидеть полный код .
Если вы используете Mybatis-Plus, проверьте официальную документацию ↓↓↓↓
Конфигурация нескольких источников данных MP
Что касается промежуточного программного обеспечения, такого как MyCat и Sharding-JDBC, мы не будем говорить о нем сегодня, мы поделимся только решением по настройке конфигурации с несколькими источниками данных.
2. Практика
Примечание .
В Springboot 2.0 зависимость HikariCP введена по умолчанию, поэтому нам не нужно вводить ее отдельно!
HikariDataSource — это основной класс операций, который HikariCP открывает пользователям для использования пулов соединений. Поэтому, когда мы создаем пул соединений HikariCP, мы фактически создаем HikariDataSource.
1. Во-первых, давайте посмотрим на конфигурацию одного источника данных, который мы настраиваем в обычных обстоятельствах.
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/db_xxx?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 指定为HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
# hikari连接池配置
hikari:
#连接池名
pool-name: HikariCP
#最小空闲连接数
minimum-idle: 5
# 空闲连接存活最大时间,默认10分钟
idle-timeout: 600000
# 连接池最大连接数,默认是10
maximum-pool-size: 10
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒
connection-timeout: 30000
# 连接测试query
connection-test-query: SELECT 1
2. Взгляните на пример конфигурации нескольких источников данных (следующая конфигурация будет иметь преимущественную силу).
spring:
datasource:
# 数据源-1
primary:
url: jdbc:mysql://127.0.0.1:3306/db_market?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
# 指定为HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
# hikari连接池配置 对应 HikariConfig 配置属性类
hikari:
pool-name: HikariCP-Primary
#最小空闲连接数
minimum-idle: 5
# 空闲连接存活最大时间,默认10分钟
idle-timeout: 600000
# 连接池最大连接数,默认是10
maximum-pool-size: 10
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒
connection-timeout: 30000
# 连接测试query
connection-test-query: SELECT 1
# 数据源-2
secondary:
url: jdbc:mysql://192.168.58.212:3306/db_market?useSSL=false&autoReconnect=true&characterEncoding=utf8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 12345678
# 指定为HikariDataSource
type: com.zaxxer.hikari.HikariDataSource
# hikari连接池配置
hikari:
pool-name: HikariCP-Secondary
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
3. Далее давайте посмотрим на множество подобных примеров, которые появляются в Интернете (хотя здесь это неправильные примеры)
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "usersDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
Для настройки нескольких источников данных здесь из-заХикариЗначение, соответствующее этому атрибуту, все еще находится на следующем уровне, поэтому приведенная выше конфигурация не эффективна.
- Здесь используется Hikari, поэтому созданный здесь компонент DataSource на самом деле является HikariDataSource.
- @ConfigurationProperties(prefix = «spring.datasource.primary») назначит свойства основного компонента соответствующим свойствам в компоненте HikariDataSource (фактически присваивая значения HikariConfig . Для получения более конкретной информации вы можете выполнить поиск по процессу инициализации DataSource На диаграмме исходного кода ниже вы можете видеть, что HikariDataSource наследует HikariConfig ).
- но,ХикариПоскольку этот атрибут является атрибутом второго уровня Spring.datasource.primary, его нельзя установить правильно (мы проверим эту проблему позже, обратите внимание на то, где LOGGER печатается в рамке на рисунке ниже).
4. Ниже приведен мой полный класс конфигурации с несколькими источниками данных (окончательная версия будет разделена на две конфигурации).
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
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.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* @ClassName: HikariDataSourceConfiguration
* @Author: lequal
* @Date: 2022/12/22
* @Description: 多数据源配置
*/
@Configuration
public class HikariDataSourceConfiguration {
@Primary
@Bean("primaryDataSourceProperties")
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
@Primary
@Bean("primaryDataSource")
@Qualifier(value = "primaryDataSource")
// 留意下面这行
@ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
public HikariDataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean("secondaryDataSourceProperties")
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
@Bean("secondaryDataSource")
@Qualifier(value = "secondaryDataSource")
// 留意下面这行
@ConfigurationProperties(prefix = "spring.datasource.secondary.hikari")
public HikariDataSource secondaryDataSource() {
return secondaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
Справочные материалы (Практика показала, что искать официальные документы, когда есть чем заняться, вполне стандартно) ↓↓↓↓↓
Пример конфигурации мультиданных официального документа Spring
Обратите внимание, что выше я отметил как первую, так и вторую конфигурации источника данных
@ConfigurationProperties(prefix = «spring.datasource.primary.hikari»)
@ConfigurationProperties(prefix = «spring.datasource.вторичный.hikari»)
Чтобы проверить проблему сбоя конфигурации, о которой мы упоминали выше, выполните следующие действия:
1. Когда мы закомментируем эти две строки, конфигурация в этот момент эквивалентна примеру ошибки, упомянутому выше.
2. Конфигурация с несколькими источниками данных подчиняется приведенному выше коду. Имена пулов соединений, которые я установил для двух источников данных:ХикариCP-Основной、ХикариCP-вторичный, в сочетании с конструктором HikariDataSource в красном поле на верхней карте, мы знаем, что имя пула данных будет напечатано при инициализации компонента.
LOGGER.info("{} - Запуск...", Configuration.getPoolName());
3.Запускаем проект и проверяем консоль.В
это время вы обнаружите, что инициализированный источник данных не тот, что нам нужен.Этого достаточно, чтобы показать, что мы настроиливесна.datasource.primary.hikariСвойства этого слоя были установлены неправильно:ХикариКонфигСредний (хотя он может работать и корректно подключаться к соответствующему источнику данных, другие конфигурации не вступили в силу), ноХикариКонфигИмя автоматически добавляется к каждому источнику данных во время инициализации. Вы можете увидеть следующий исходный код (HikariConfig.class).
Если в это время мы
настроим первый и второй источники данных с помощью @ConfigurationProperties(prefix = «spring.datasource. первичный .hikari"), а затем запустите проект, вы можете ясно видеть, что заданные нами свойства применяются кХикариКонфигВ
настоящее время конфигурация пула соединений hikari может быть прочитана в обычном режиме.
Если вы не понимаете, можете посмотреть процесс инициализации DataSource самостоятельно.
5. Далее проведем тест
import cn.hutool.json.JSONUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
/**
* @ClassName: MultiDataSourceTest
* @Author: lequal
* @Date: 2022/12/22
* @Description:
*/
@SpringBootTest
public class MultiDataSourceTest {
@Resource
private JdbcTemplate primaryJdbcTemplate;
@Resource
private JdbcTemplate secondaryJdbcTemplate;
@Resource(name = "primaryDataSource")
private DataSource primaryDataSource;
@Resource(name = "secondaryDataSource")
private DataSource secondaryDataSource;
@Test
public void testPrimaryDataSourceConnect() {
String sql = "SELECT * FROM `apk_category`";
List<Map<String, Object>> result = primaryJdbcTemplate.queryForList(sql);
System.out.println("primary data source :\t"+ JSONUtil.toJsonStr(result));
}
@Test
public void testSecondaryDataSourceConnect() {
String sql = "SELECT * FROM `apk_category`";
List<Map<String, Object>> result = secondaryJdbcTemplate.queryForList(sql);
System.out.println("secondary data source :\t"+ JSONUtil.toJsonStr(result));
}
@Test
void testGetConnection() {
try (Connection connection = primaryDataSource.getConnection()) {
System.out.println("获取到的primaryDataSource连接对象" + connection);
} catch (SQLException e) {
e.printStackTrace();
}
try (Connection connection = secondaryDataSource.getConnection()) {
System.out.println("获取到的secondaryDataSource连接对象" + connection);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3. Полный код
- PrimaryDataSourceConfiguration.class
import com.zaxxer.hikari.HikariDataSource;
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.autoconfigure.jdbc.DataSourceProperties;
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.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
/**
* @ClassName: PrimaryDataSourceConfiguration
* @Author: lequal
* @Date: 2022/12/22
* @Description: 主要数据源配置
*/
@Configuration
@MapperScan(basePackages = "com.market.mapper", sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfiguration {
/**
* 指定mapper xml文件路径
*/
public static final String MAPPER_LOCATION = "classpath:mapper/*.xml";
/**
* @Author: lequal
* @Description 获取一级的属性
* @Date 2022/12/22 16:48
* @return org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
*/
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
/**
* @Author: lequal
* @Description 获取下一级的属性(hikari)并创建数据源
* @Date 2022/12/22 16:48
* @return com.zaxxer.hikari.HikariDataSource
*/
@Primary
@Bean("primaryDataSource")
@Qualifier(value = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary.hikari")
public HikariDataSource primaryDataSource() {
return primaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/**
* @Author: lequal
* @Description 配置事务管理器
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.transaction.PlatformTransactionManager
*/
@Bean(name = "primaryTransactionManager")
@Primary
public PlatformTransactionManager primaryTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* @Author: lequal
* @Description 自定义SQLSession工厂
* @Date 2022/12/22 16:50
* @param dataSource
* @return org.apache.ibatis.session.SqlSessionFactory
*/
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(PrimaryDataSourceConfiguration.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
/**
* @Author: lequal
* @Description 创建JDBC模板
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.jdbc.core.JdbcTemplate
*/
@Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("primaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
- SecondaryDataSourceConfiguration.class
import com.zaxxer.hikari.HikariDataSource;
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.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 javax.sql.DataSource;
/**
* @ClassName: HikariDataSourceConfiguration
* @Author: lequal
* @Date: 2022/12/22
* @Description: 副数据源配置
*/
@Configuration
@MapperScan(basePackages = "com.market.mapper2", sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfiguration {
/**
* 指定mapper xml文件路径
*/
public static final String MAPPER_LOCATION = "classpath:mapper2/*.xml";
/**
* @Author: lequal
* @Description 获取一级的属性
* @Date 2022/12/22 16:48
* @return org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
*/
@ConfigurationProperties("spring.datasource.secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
/**
* @Author: lequal
* @Description 获取下一级的属性(hikari)并创建数据源
* @Date 2022/12/22 16:48
* @return com.zaxxer.hikari.HikariDataSource
*/
@Bean("secondaryDataSource")
@Qualifier(value = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary.hikari")
public HikariDataSource secondaryDataSource() {
return secondaryDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
/**
* @Author: lequal
* @Description 配置事务管理器
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.transaction.PlatformTransactionManager
*/
@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(SecondaryDataSourceConfiguration.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
/**
* @Author: lequal
* @Description 创建JDBC模板
* @Date 2022/12/22 16:48
* @param dataSource
* @return org.springframework.jdbc.core.JdbcTemplate
*/
@Bean(name = "secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
Поясним:
когда мы раньше использовали @Transactional, мы не устанавливали менеджер транзакций через value илиtransactionManager.Почему это так?
Это связано с тем, что мы определили только один менеджер транзакций в контейнере Spring (реализуя метод annotationDrivenTransactionManager() интерфейса TransactionManagementConfigurer. Менеджер транзакций, возвращаемый этим методом, используется системой по умолчанию). Когда Spring запускает транзакцию, по умолчанию он будет по типу. Найдите менеджер транзакций в контейнере. В контейнере есть только один, поэтому я использую его. Если есть несколько менеджеров транзакций, Spring не знает, какой менеджер транзакций использовать, если вы не указывайте это.
При использовании транзакций это выглядит так: @Transactional(transactionManager = «transactionManager1», propagation = Propagation.REQUIRED).В декларативных транзакциях вы указываете менеджер транзакций, который вам нужно использовать, то есть транзакцию, которую мы только что настроили вручную для каждых данных. источник, менеджер.
Если в системе имеется несколько менеджеров транзакций и вам нужно, чтобы система указала один из менеджеров транзакций по умолчанию, вам нужно только написать класс конфигурации самостоятельно, чтобы реализовать интерфейс TransactionManagementConfigurer и переопределить **annotationDrivenTransactionManager()**.
Исходный код выглядит следующим образом:
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionManager;
public interface TransactionManagementConfigurer {
TransactionManager annotationDrivenTransactionManager();
}
Пример:
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import javax.annotation.Resource;
/**
* @ClassName: CustomTransactionManagement
* @Author: lequal
* @Date: 2023/04/10
* @Description: 系统启动时注入默认的事务管理器
*/
@Configuration
public class CustomTransactionManagement implements TransactionManagementConfigurer {
// 假设自己需要把主数据源配置的事务管理器作为默认的,也可以直接在PrimaryDataSourceConfiguration类实现接口
@Resource(name = "primaryTransactionManager")
private PlatformTransactionManager txManager;
/**
* @Author: lequal
* @Description 其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
* @Date 2023/04/10 8:55
* @return org.springframework.transaction.TransactionManager
*/
@Override
public TransactionManager annotationDrivenTransactionManager() {
return txManager;
}
}
Если транзакция не вступает в силу, включите поддержку транзакций вручную: @EnableTransactionManagement. По умолчанию поддержка транзакций настраивается во время автоматической сборки. Подробную информацию см. в исходном коде класса TransactionAutoConfiguration .
Как вы получите различные источники данных в дальнейшем, зависит от вас.