多数据源核心概念
多数据源是指在一个应用程序中同时连接和使用多个数据库的能力。在实际开发中,我们经常会遇到以下场景需要多数据源:
- 同时连接生产数据库和报表数据库
- 读写分离场景(主库写,从库读)
- 微服务架构中需要访问其他服务的数据库
- 多租户系统中每个租户有独立数据库
多数据源实现示例
多数据源的配置文件以及配置类
application.yml 配置示例
spring:
datasource:
jdbc-url: jdbc:mysql://localhost:3306/db1 # 主数据源
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.driver
hikari:
pool-name: primaryhikaripool
# 最大连接数
maximum-pool-size: 20
# 最小空闲连接
minimum-idle: 5
# 空闲连接超时时间(ms)
idle-timeout: 30000
# 连接最大生命周期(ms)
max-lifetime: 1800000
# 获取连接超时时间(ms)
connection-timeout: 30000
connection-test-query: select 1
second-datasource:
jdbc-url: jdbc:mysql://localhost:3306/db2 # 主数据源
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.driver
hikari:
pool-name: secondhikaripool
maximum-pool-size: 20
minimum-idle: 5
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: select 1多数据源配置类
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 javax.sql.datasource;
@configuration
public class dbconfig {
@bean("db1datasourceproperties")
@configurationproperties(prefix = "spring.datasource")
public datasourceproperties db1datasourceproperties() {
return new datasourceproperties();
}
@bean(name = "db1datasource")
public datasource datasource() {
return db1datasourceproperties().initializedatasourcebuilder().build();
}
@bean("db2datasourceproperties")
@configurationproperties(prefix = "spring.second-datasource")
public datasourceproperties db2datasourceproperties() {
return new datasourceproperties();
}
@bean(name = "db2datasource")
public datasource db2datasource() {
return db2datasourceproperties().initializedatasourcebuilder().build();
}
}
禁用默认数据源
多数据源时需在主类排除自动配置
@springbootapplication(exclude = {datasourceautoconfiguration.class})
public class app {
public static void main(string[] args) {
springapplication.run(app.class, args);
}
}jpa 多数据源配置
主数据源 jap 配置
import com.querydsl.jpa.impl.jpaqueryfactory;
import org.springframework.beans.factory.annotation.qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.jpaproperties;
import org.springframework.boot.orm.jpa.entitymanagerfactorybuilder;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.jpa.repository.config.enablejparepositories;
import org.springframework.orm.jpa.jpatransactionmanager;
import org.springframework.orm.jpa.localcontainerentitymanagerfactorybean;
import org.springframework.orm.jpa.vendor.hibernatejpavendoradapter;
import org.springframework.transaction.platformtransactionmanager;
import org.springframework.transaction.annotation.enabletransactionmanagement;
import javax.persistence.entitymanager;
import javax.sql.datasource;
import java.util.hashmap;
import java.util.objects;
@configuration
// 启用 spring 的事务管理功能,允许使用 @transactional 注解来管理事务
@enabletransactionmanagement
// 启用 jpa 仓库的自动扫描和注册功能
@enablejparepositories(
// 指定要扫描的 jpa 仓库接口所在的包路径
basepackages = "com.example.db1",
// 指定使用的实体管理器工厂的 bean 名称
entitymanagerfactoryref = "db1entitymanagerfactory",
// 指定使用的事务管理器的 bean 名称
transactionmanagerref = "db1transactionmanager"
)
public class db1jpaconfig {
/**
* 创建实体管理器工厂的 bean,并将其标记为主要的实体管理器工厂 bean
*/
@bean(name = "db1entitymanagerfactory")
public localcontainerentitymanagerfactorybean entitymanagerfactory(
@qualifier("db1datasource")datasource datasource,
jpaproperties jpaproperties) {
return new entitymanagerfactorybuilder(new hibernatejpavendoradapter(),
new hashmap<>(), null)
// 设置数据源
.datasource(datasource)
// 指定要扫描的实体类所在的包路径
.packages("com.example.db1")
// 设置持久化单元的名称
.persistenceunit("db1")
// 设置 jpa 的属性
.properties(jpaproperties.getproperties())
.build();
}
/**
* 创建事务管理器的 bean,并将其标记为主要的事务管理器 bean
*/
@bean(name = "db1transactionmanager")
public platformtransactionmanager transactionmanager(
@qualifier("db1entitymanagerfactory") localcontainerentitymanagerfactorybean entitymanagerfactory) {
return new jpatransactionmanager(objects.requirenonnull(entitymanagerfactory.getobject()));
}
/**
* querydsl的核心组件
*/
@bean(name = "db1jpaqueryfactory")
public jpaqueryfactory db1jpaqueryfactory(
@qualifier("db1entitymanagerfactory") entitymanager entitymanager) {
return new jpaqueryfactory(entitymanager);
}
}从数据源 jap 集成配置(略)
mybatis 多数据源配置
主数据源 mybatis 配置
import org.apache.ibatis.session.sqlsessionfactory;
import org.mybatis.spring.sqlsessionfactorybean;
import org.mybatis.spring.sqlsessiontemplate;
import org.mybatis.spring.annotation.mapperscan;
import org.springframework.beans.factory.annotation.qualifier;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.core.io.support.pathmatchingresourcepatternresolver;
import org.springframework.jdbc.datasource.datasourcetransactionmanager;
import org.springframework.transaction.platformtransactionmanager;
import javax.sql.datasource;
@configuration
// 此注解用于指定 mybatis mapper 接口的扫描范围和对应的 sqlsessionfactory 引用
@mapperscan(
// 指定要扫描的 mapper 接口所在的基础包路径
basepackages = "com.example.mapper.db1",
// 配置使用的 sqlsessionfactory bean 的名称
sqlsessionfactoryref = "db1sqlsessionfactory"
)
public class db1mybatisconfig {
/**
* 创建 sqlsessionfactory bean
*/
@bean("db1sqlsessionfactory")
public sqlsessionfactory db1sqlsessionfactory(
@qualifier("db1datasource") datasource datasource) throws exception {
// 创建 sqlsessionfactorybean 实例,用于创建 sqlsessionfactory
sqlsessionfactorybean sessionfactory = new sqlsessionfactorybean();
// 设置 sqlsessionfactory 使用的数据源
sessionfactory.setdatasource(datasource);
// 设置 mapper xml 文件的位置,使用 pathmatchingresourcepatternresolver 来查找匹配的资源
sessionfactory.setmapperlocations(
new pathmatchingresourcepatternresolver()
.getresources("classpath:mapper/db1/*.xml"));
// 获取并返回 sqlsessionfactory 实例
return sessionfactory.getobject();
}
/**
* 创建 sqlsessiontemplate bean
*/
@bean("db1sqlsessiontemplate")
public sqlsessiontemplate db1sqlsessiontemplate(
@qualifier("db1sqlsessionfactory") sqlsessionfactory sqlsessionfactory) {
// 创建并返回 sqlsessiontemplate 实例,用于简化 mybatis 的操作
return new sqlsessiontemplate(sqlsessionfactory);
}
/**
* 创建事务管理器的 bean,并将其标记为主要的事务管理器 bean
*/
@bean("db1transactionmanager")
public platformtransactionmanager transactionmanager(
@qualifier("db1datasource") datasource datasource) {
return new datasourcetransactionmanager(datasource);
}
}
从数据源 mybatis 配置(略)
事务管理:跨数据源事务处理
单数据源事务
在单数据源场景下,spring的事务管理非常简单:
@service
public class accountservice {
@transactional // 使用默认事务管理器
public void transfer(long fromid, long toid, bigdecimal amount) {
// do some thing ...
}
}
多数据源事务挑战
多数据源事务面临的主要问题是分布式事务的挑战。spring 的 @transactional 注解默认只能管理单个事务管理器,无法直接协调多个数据源的事务。
解决方案对比:
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| jta (java transaction api) | 使用全局事务协调器 | 强一致性 | 性能开销大,配置复杂 | 需要强一致性的金融系统 |
| 最终一致性 (saga模式) | 通过补偿操作实现 | 高性能,松耦合 | 实现复杂,需要补偿逻辑 | 高并发,可接受短暂不一致 |
| 本地消息表 | 通过消息队列保证 | 可靠性高 | 需要额外表存储消息 | 需要可靠异步处理的场景 |
事务管理器:datasourcetransactionmanager 和 jpatransactionmanager
datasourcetransactionmanager 和 jpatransactionmanager 是 spring 框架中针对不同持久层技术的事务管理器。
技术栈适配差异
1.datasourcetransactionmanager
适用场景:纯 jdbc、mybatis、jdbctemplate 等基于原生 sql 的数据访问技术
事务控制对象:直接管理 java.sql.connection ,通过数据库连接实现事务
局限性:
- 无法自动绑定 jpa 或 hibernate 的 entitymanager/session 到当前事务上下文
- 混合使用 jdbc 和 jpa 时可能导致连接隔离(各自使用独立连接),破坏事务一致性
2.jpatransactionmanager
适用场景:jpa 规范实现(如 hibernate、eclipselink)
事务控制对象:管理 jpa entitymanager,通过其底层连接协调事务
核心优势:
- 自动将 entitymanager 绑定到线程上下文,确保同一事务中多次操作使用同一连接
- 支持 jpa 的延迟加载(lazy loading)、缓存同步等特性
3.混合技术栈的特殊情况
混合技术栈需严格隔离事务管理器,并考虑分布式事务需求
jpa操作使用jpatransactionmanager,mybatis操作使用datasourcetransactionmanager
跨数据源事务需引入分布式事务(如atomikos),否则不同数据源的事务无法保证原子性
若一个 service 方法同时使用 jpa和 mybatis(未验证):
- 使用 datasourcetransactionmanager 可能导致两个操作使用不同连接,违反 acid
- 使用 jpatransactionmanager 能保证两者共享同一连接(因 jpa 底层复用 datasource 连接)
事务同步机制对比
| 特性 | datasourcetransactionmanager | jpatransactionmanager |
|---|---|---|
| 连接资源管理 | 直接管理 connection | 通过 entitymanager 间接管理连接 |
| 跨技术兼容性 | 仅限 jdbc 系技术 | 支持 jpa 及其混合场景(如 jpa+jdbc) |
| 高级 orm 功能支持 | 不支持(如延迟加载) | 完整支持 jpa 特性 |
| 配置复杂度 | 简单(仅需 datasource) | 需额外配置 entitymanagerfactory |
多数据源事务使用
事务配置详见上文
多数据源事务使用示例
import org.springframework.transaction.annotation.transactional;
@service
public class accountservice {
@transactional(transactionmanager = "db1transactionmanager") // 指定事务管理器
public void transfer(long fromid, long toid, bigdecimal amount) {
// do some thing ...
}
}
基于 abstractroutingdatasource 的动态数据源
动态数据源上下文
public class dynamicdatasourcecontextholder {
// 使用threadlocal保证线程安全
private static final threadlocal<string> context_holder = new threadlocal<>();
// 数据源列表
public static final string primary_ds = "primary";
public static final string secondary_ds = "secondary";
public static void setdatasourcetype(string dstype) {
context_holder.set(dstype);
}
public static string getdatasourcetype() {
return context_holder.get();
}
public static void cleardatasourcetype() {
context_holder.remove();
}
}
动态数据源配置
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.jdbc.datasource.lookup.abstractroutingdatasource;
import javax.sql.datasource;
import java.util.hashmap;
import java.util.map;
@configuration
public class dynamicdatasourceconfig {
/**
* 创建动态数据源 bean,并将其设置为主要的数据源 bean
*/
@bean
@primary
public datasource dynamicdatasource(
@qualifier("db1datasource") datasource db1datasource,
@qualifier("db2datasource") datasource db2datasource) {
// 用于存储目标数据源的映射,键为数据源标识,值为数据源实例
map<object, object> targetdatasources = new hashmap<>();
// 将主数据源添加到目标数据源映射中,使用自定义的主数据源标识
targetdatasources.put(dynamicdatasourcecontextholder.primary_ds, db1datasource);
// 将从数据源添加到目标数据源映射中,使用自定义的从数据源标识
targetdatasources.put(dynamicdatasourcecontextholder.secondary_ds, db2datasource);
// 创建自定义的动态数据源实例
dynamicdatasource dynamicdatasource = new dynamicdatasource();
// 设置动态数据源的目标数据源映射
dynamicdatasource.settargetdatasources(targetdatasources);
// 设置动态数据源的默认目标数据源为主数据源
dynamicdatasource.setdefaulttargetdatasource(db1datasource);
return dynamicdatasource;
}
/**
* 自定义动态数据源类,继承自 abstractroutingdatasource
*/
private static class dynamicdatasource extends abstractroutingdatasource {
/**
* 确定当前要使用的数据源的标识
* @return 当前数据源的标识
*/
@override
protected object determinecurrentlookupkey() {
// 从上下文持有者中获取当前要使用的数据源类型
return dynamicdatasourcecontextholder.getdatasourcetype();
}
}
}
基于aop的读写分离实现
@retention(retentionpolicy.runtime)
@target(elementtype.method)
public @interface readonly {
// 标记为读操作
}
@aspect
@component
public class readwritedatasourceaspect {
@before("@annotation(readonly)")
public void beforeswitchdatasource(joinpoint point, readonly readonly) {
dynamicdatasourcecontextholder.setdatasourcetype(dynamicdatasourcecontextholder.secondary_ds);
}
@after("@annotation(readonly)")
public void afterswitchdatasource(joinpoint point, readonly readonly) {
dynamicdatasourcecontextholder.cleardatasourcetype();
}
}使用示例
@service
public class productservice {
@autowired
private productrepository productrepository;
@transactional
public void createproduct(product product) {
// 默认使用主数据源(写)
productrepository.save(product);
}
@readonly // 执行该注解标记的方法时,前后都会执行readwritedatasourceaspect切面类方法
@transactional
public product getproduct(long id) {
// 使用从数据源(读)
return productrepository.findbyid(id).orelse(null);
}
@readonly
@transactional
public list<product> listproducts() {
// 使用从数据源(读)
return productrepository.findall();
}
}常见问题与解决方案
典型问题排查表
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| jta (java transaction api) | 使用全局事务协调器 | 强一致性 | 性能开销大,配置复杂 | 需要强一致性的金融系统 |
| 最终一致性 (saga模式) | 通过补偿操作实现 | 高性能,松耦合 | 实现复杂,需要补偿逻辑 | 高并发,可接受短暂不一致 |
| 本地消息表 | 通过消息队列保证 | 可靠性高 | 需要额外表存储消息 | 需要可靠异步处理的场景 |
数据源切换失败案例分析
问题描述:
在动态数据源切换场景下,有时切换不生效,仍然使用默认数据源。
原因分析:
- 数据源切换代码被异常绕过,未执行
- 线程池场景下线程复用导致上下文污染
- aop 顺序问题导致切换时机不对
解决方案:
@aspect
@component
@order(ordered.highest_precedence) // 确保最先执行
public class datasourceaspect {
@around("@annotation(targetdatasource)")
public object around(proceedingjoinpoint joinpoint, targetdatasource targetdatasource) throws throwable {
string oldkey = dynamicdatasourcecontextholder.getdatasourcetype();
try {
dynamicdatasourcecontextholder.setdatasourcetype(targetdatasource.value());
return joinpoint.proceed();
} finally {
// 恢复为原来的数据源
if (oldkey != null) {
dynamicdatasourcecontextholder.setdatasourcetype(oldkey);
} else {
dynamicdatasourcecontextholder.cleardatasourcetype();
}
}
}
}
// 线程池配置确保清理上下文
@configuration
public class threadpoolconfig {
@bean
public executorservice asyncexecutor() {
threadpooltaskexecutor executor = new threadpooltaskexecutor();
executor.setcorepoolsize(5);
executor.setmaxpoolsize(10);
executor.setqueuecapacity(100);
executor.setthreadnameprefix("async-");
executor.settaskdecorator(runnable -> {
string dskey = dynamicdatasourcecontextholder.getdatasourcetype();
return () -> {
try {
if (dskey != null) {
dynamicdatasourcecontextholder.setdatasourcetype(dskey);
}
runnable.run();
} finally {
dynamicdatasourcecontextholder.cleardatasourcetype();
}
};
});
executor.initialize();
return executor.getthreadpoolexecutor();
}
}多数据源与缓存集成
当多数据源与缓存(如 redis)一起使用时,需要注意缓存键的设计:
@service
public class cacheduserservice {
@autowired
private primaryuserrepository primaryuserrepository;
@autowired
private secondaryuserrepository secondaryuserrepository;
@autowired
private redistemplate<string, user> redistemplate;
private string getcachekey(string source, long userid) {
return string.format("user:%s:%d", source, userid);
}
@cacheable(value = "users", key = "#root.target.getcachekey('primary', #userid)")
public user getprimaryuser(long userid) {
return primaryuserrepository.findbyid(userid).orelse(null);
}
@cacheable(value = "users", key = "#root.target.getcachekey('secondary', #userid)")
public user getsecondaryuser(long userid) {
return secondaryuserrepository.findbyid(userid).orelse(null);
}
@cacheevict(value = "users", allentries = true)
public void clearallusercache() {
// 清除所有用户缓存
}
}
总结与扩展
技术选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单多数据源,无交叉访问 | 独立配置多个数据源 | 简单直接,易于维护 |
| 需要动态切换数据源 | abstractroutingdatasource | 灵活,可运行时决定数据源 |
| 需要强一致性事务 | jta(xa) | 保证acid,但性能较低 |
| 高并发,最终一致性可接受 | saga模式 | 高性能,松耦合 |
| 读写分离 | aop+注解方式 | 透明化,对业务代码侵入小 |
以上就是springboot进行多数据源配置的详细步骤的详细内容,更多关于springboot多数据源配置的资料请关注代码网其它相关文章!
发表评论