7.3 KiB
双数据源说明
本服务使用两套手工配置的 JPA 数据源,不依赖动态数据源框架。
| 侧 | 数据库 | 用途 | DAO 包 | Entity 包 | 事务管理器 |
|---|---|---|---|---|---|
| Primary | MySQL | 数据字典、UI 设置(读写) | domain.primary.dao |
domain.primary.entity |
transactionManagerPrimary |
| Secondary | Oracle | 机场基础数据、季度计划(当前只读) | domain.secondary.dao |
domain.secondary.entity |
transactionManagerSecondary |
Secondary 没有技术上的强制只读保护;不要在其 DAO 上调用
save()或delete(),除非已确认 Oracle 写入需求和事务方案。
核心思路:启动时把 DAO 固定接到一套数据库
这不是按请求、用户或 SQL 内容动态选择数据源;应用启动时,Spring 就已经为每个 DAO 创建好代理,并把它固定绑定到 Primary 或 Secondary。
SysAirlineDao
位于 domain.secondary.dao
↓ 被 SecondaryConfig 的 @EnableJpaRepositories 扫描
↓ 创建 Repository 代理,绑定 entityManagerFactorySecondary
↓ entityManagerFactorySecondary 使用 secondaryDataSource
↓ 此 DAO 的所有 JPA 操作都访问 Oracle
DatadicItemDao
位于 domain.primary.dao
↓ 被 PrimaryConfig 的 @EnableJpaRepositories 扫描
↓ 创建 Repository 代理,绑定 entityManagerFactoryPrimary
↓ entityManagerFactoryPrimary 使用 primaryDataSource
↓ 此 DAO 的所有 JPA 操作都访问 MySQL
Entity 的包路径服务于同一个绑定关系:Primary EMF 只认识 Primary Entity,Secondary EMF 只认识 Secondary Entity。也就是说,一个 DAO 所操作的 Entity 必须被同一套 EMF管理。
一句话记忆:DAO 在哪个
*.dao根包,就由该包对应的 Config 创建代理;该 Config 指向哪一个 EMF / DataSource,DAO 就访问哪一个数据库。
Java 包不会让 JVM 把类“加载到不同地方”——所有类仍在同一个应用进程中。包名只是 Spring/JPA 的扫描筛选条件;真正完成数据库路由的是 @EnableJpaRepositories、EMF 和 DataSource 之间的显式引用。
不同 package 的类如何被装载和绑定
这里的“装载”分两件事:Entity 注册到哪个 JPA EMF,以及 DAO 代理使用哪个 EMF/事务管理器。它们不是靠类名或注解自动猜测,而是由两套 Config 中的扫描包明确指定。
1. Entity:.packages() 决定注册到哪个 EMF
PrimaryConfig 创建 MySQL 的 EMF 时只扫描 Primary Entity 包:
// PrimaryConfig.java
builder
.dataSource(primaryDataSource)
.packages("com.gzzn.omms.adminapi.domain.primary.entity")
.persistenceUnit("primaryPersistenceUnit")
.build();
SecondaryConfig 创建 Oracle 的 EMF 时只扫描 Secondary Entity 包:
// SecondaryConfig.java
builder
.dataSource(secondaryDataSource)
.packages("com.gzzn.omms.adminapi.domain.secondary.entity")
.persistenceUnit("secondaryPersistenceUnit")
.build();
.packages() 会扫描指定包及其子包中的 @Entity(以及关联的 JPA 映射类)。所以:
domain.primary.entity.dictionary.DatadicItem会注册到primaryPersistenceUnit,其 SQL 发往 MySQL。domain.secondary.entity.baiscdata.SysAirline会注册到secondaryPersistenceUnit,其 SQL 发往 Oracle。baiscdata虽然是现有目录中的拼写,但它仍是domain.secondary.entity的子包,会被扫描到;不要为了改名直接移动类,除非同步验证所有引用和映射。
@Entity 本身不是普通 Spring Service Bean;它由对应 EMF 扫描并维护映射元数据。放错包时,它会被注册到错误一侧的 EMF,后续 SQL 会查错数据库。
2. DAO:@EnableJpaRepositories 决定代理绑定到哪个 EMF
两个配置类分别扫描不同 DAO 包,并显式引用本侧的 EMF 和事务管理器:
// PrimaryConfig.java
@EnableJpaRepositories(
basePackages = "com.gzzn.omms.adminapi.domain.primary.dao",
entityManagerFactoryRef = "entityManagerFactoryPrimary",
transactionManagerRef = "transactionManagerPrimary")
// SecondaryConfig.java
@EnableJpaRepositories(
basePackages = "com.gzzn.omms.adminapi.domain.secondary.dao",
entityManagerFactoryRef = "entityManagerFactorySecondary",
transactionManagerRef = "transactionManagerSecondary")
应用启动时,Spring Data 会扫描这些包中的 CrudRepository 接口并创建代理:
primary.dao 中的 DAO → Primary EMF / Primary TM → MySQL
secondary.dao 中的 DAO → Secondary EMF / Secondary TM → Oracle
因此 Controller 或 Service 注入 SysAirlineDao 时,拿到的是已绑定 Oracle 的代理;业务调用方不需要、也不应根据 DAO 名称自行切换数据源。
3. 新增类时按此配对
MySQL: primary.entity.<业务包>.XxxEntity
primary.dao.<业务包>.XxxDao
Oracle: secondary.entity.<业务包>.XxxEntity
secondary.dao.<业务包>.XxxDao
只要类放在上述根包或任意子包内,现有扫描配置即可发现它们,无需新增扫描配置。若要使用第三套根包或第三个数据源,才需要新增对应的 DataSource、EMF、事务管理器和 @EnableJpaRepositories 配置。
配置位置
| 内容 | 代码位置 |
|---|---|
| MySQL DataSource | config/PrimaryDataSourceConfig.java |
| Oracle DataSource | config/SecondaryDataSourceConfig.java |
| MySQL JPA / DAO 绑定 | config/PrimaryConfig.java |
| Oracle JPA / DAO 绑定 | config/SecondaryConfig.java |
| 环境连接信息 | src/main/resources/application-{dev,test,pro}.yml |
Primary 的 DataSource、EntityManager、EMF 和事务管理器均标记 @Primary。这只是在未明确指定 Bean 时提供默认值;Repository 实际使用哪一侧,仍由各自的 @EnableJpaRepositories 配置决定。
新增表访问的做法
新增 MySQL 表
- Entity 放到
domain.primary.entity下。 - DAO 放到
domain.primary.dao下。 - 需要写操作时使用
transactionManagerPrimary。
新增 Oracle 表
- Entity 放到
domain.secondary.entity下。 - DAO 放到
domain.secondary.dao下。 - 默认按只读接口实现;若确实需要写操作,显式指定
transactionManagerSecondary,并先确认 Oracle 权限与业务影响。
常见错误
| 问题 | 后果 | 排查/处理 |
|---|---|---|
| Entity 或 DAO 放错侧 | 可能能启动,首次查询才报目标库不存在表 | 检查 Entity、DAO 包路径及两套 Config 的扫描包 |
| 只新增 DAO、未新增正确 Entity | JPA 找不到实体或映射不完整 | Entity 与 DAO 必须成对放在同侧 |
裸写 @Transactional |
默认使用 Primary 事务管理器 | 显式填写事务管理器名称 |
| 一个业务操作同时写两库 | 不具备原子性,可能部分成功 | 避免跨库写;需要原子性时引入分布式事务方案 |
| 漏配某环境数据源 | 应用启动时 Bean 装配失败 | 三个 profile 都补齐对应数据源配置 |
事务示例
@Transactional(transactionManager = "transactionManagerPrimary")
public void saveMysqlData() {
// 调用 primary DAO
}
@Transactional(transactionManager = "transactionManagerSecondary")
public void saveOracleData() {
// 调用 secondary DAO;仅在确认允许写 Oracle 时使用
}