Files

7.3 KiB
Raw Permalink Blame History

双数据源说明

本服务使用两套手工配置的 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 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 包:

// 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 的 DataSourceEntityManager、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 都补齐对应数据源配置

事务示例

@Transactional(transactionManager = "transactionManagerPrimary")
public void saveMysqlData() {
    // 调用 primary DAO
}

@Transactional(transactionManager = "transactionManagerSecondary")
public void saveOracleData() {
    // 调用 secondary DAO;仅在确认允许写 Oracle 时使用
}