Files

162 lines
7.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 双数据源说明
本服务使用两套手工配置的 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。**
```text
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 EntitySecondary EMF 只认识 Secondary Entity。也就是说,一个 DAO 所操作的 Entity 必须被**同一套 EMF**管理。
> **一句话记忆:DAO 在哪个 `*.dao` 根包,就由该包对应的 Config 创建代理;该 Config 指向哪一个 EMF / DataSourceDAO 就访问哪一个数据库。**
Java 包不会让 JVM 把类“加载到不同地方”——所有类仍在同一个应用进程中。包名只是 Spring/JPA 的**扫描筛选条件**;真正完成数据库路由的是 `@EnableJpaRepositories`、EMF 和 DataSource 之间的显式引用。
## 不同 package 的类如何被装载和绑定
这里的“装载”分两件事:**Entity 注册到哪个 JPA EMF**,以及 **DAO 代理使用哪个 EMF/事务管理器**。它们不是靠类名或注解自动猜测,而是由两套 Config 中的扫描包明确指定。
### 1. Entity`.packages()` 决定注册到哪个 EMF
`PrimaryConfig` 创建 MySQL 的 EMF 时只扫描 Primary Entity 包:
```java
// PrimaryConfig.java
builder
.dataSource(primaryDataSource)
.packages("com.gzzn.omms.adminapi.domain.primary.entity")
.persistenceUnit("primaryPersistenceUnit")
.build();
```
`SecondaryConfig` 创建 Oracle 的 EMF 时只扫描 Secondary Entity 包:
```java
// 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 和事务管理器:
```java
// 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` 接口并创建代理:
```text
primary.dao 中的 DAO → Primary EMF / Primary TM → MySQL
secondary.dao 中的 DAO → Secondary EMF / Secondary TM → Oracle
```
因此 Controller 或 Service 注入 `SysAirlineDao` 时,拿到的是已绑定 Oracle 的代理;业务调用方不需要、也不应根据 DAO 名称自行切换数据源。
### 3. 新增类时按此配对
```text
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 表
1. Entity 放到 `domain.primary.entity` 下。
2. DAO 放到 `domain.primary.dao` 下。
3. 需要写操作时使用 `transactionManagerPrimary`
### 新增 Oracle 表
1. Entity 放到 `domain.secondary.entity` 下。
2. DAO 放到 `domain.secondary.dao` 下。
3. 默认按只读接口实现;若确实需要写操作,显式指定 `transactionManagerSecondary`,并先确认 Oracle 权限与业务影响。
## 常见错误
| 问题 | 后果 | 排查/处理 |
|---|---|---|
| Entity 或 DAO 放错侧 | 可能能启动,首次查询才报目标库不存在表 | 检查 Entity、DAO 包路径及两套 Config 的扫描包 |
| 只新增 DAO、未新增正确 Entity | JPA 找不到实体或映射不完整 | Entity 与 DAO 必须成对放在同侧 |
| 裸写 `@Transactional` | 默认使用 Primary 事务管理器 | 显式填写事务管理器名称 |
| 一个业务操作同时写两库 | 不具备原子性,可能部分成功 | 避免跨库写;需要原子性时引入分布式事务方案 |
| 漏配某环境数据源 | 应用启动时 Bean 装配失败 | 三个 profile 都补齐对应数据源配置 |
## 事务示例
```java
@Transactional(transactionManager = "transactionManagerPrimary")
public void saveMysqlData() {
// 调用 primary DAO
}
@Transactional(transactionManager = "transactionManagerSecondary")
public void saveOracleData() {
// 调用 secondary DAO;仅在确认允许写 Oracle 时使用
}
```