多数据源核心概念
多数据源是指在一个应用程序中同时连接和使用多个数据库的能力。在实际开发中,我们经常会遇到以下场景需要多数据源:
- 同时连接生产数据库和报表数据库
- 读写分离场景(主库写,从库读)
- 微服务架构中需要访问其他服务的数据库
- 多租户系统中每个租户有独立数据库
多数据源实现示例
多数据源的配置文件以及配置类
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多数据源配置的资料请关注代码网其它相关文章!
发表评论