SpringBoot配置MariaDB多数据源报Unknown entity及空指针异常
问题场景
开发对接users、data两个MariaDB数据源的SpringBoot应用,初始配置逻辑如下:
- 为两个数据源分别编写独立配置类,手动定义
DataSource、LocalContainerEntityManagerFactoryBean、JpaTransactionManager相关Bean - 通过
@EnableJpaRepositories注解分别指定两个数据源对应的Repository扫描路径、实体类扫描包路径
启动过程中连续遇到两类报错:
- 在
@PostConstruct标注的初始化方法中执行Privilege实体持久化操作时,抛出异常导致应用部署失败:
注释掉该持久化语句后应用可正常启动,但写入数据库的所有字段值全为null。java.lang.IllegalArgumentException: Unknown entity: it.unict.spring.application.persistence.model.user.Privilege - 参考调整方案修改配置:改用
EntityManagerFactoryBuilder构建EntityManagerFactory、为两个数据源指定独立persistenceUnit,同时在主启动类通过@Qualifier注解指定注入对应EntityManagerFactory Bean后,抛出空指针异常,应用依旧无法启动:Cannot invoke "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.getObject()" because "this.userEntityManagerFactory" is null
根因分析
- 针对
Unknown entity+插入字段全为null的问题:核心是实体扫描配置和持久化单元绑定错误。要么是users数据源对应的实体扫描包路径没有精准覆盖Privilege类所在位置,要么是两个数据源的实体扫描范围重叠,导致JPA没有将Privilege实体和users库的持久化上下文绑定,既无法识别该实体,就算勉强启动也拿不到实体字段和表字段的映射关系,插入数据时自然全为null。 - 针对userEntityManagerFactory为null的空指针问题:通常由三类配置错误触发:
@Qualifier指定的Bean名称和配置类中实际声明的EntityManagerFactory Bean名称不匹配,Spring找不到对应Bean时会注入null- 数据源配置类漏加
@Configuration注解,没有被Spring扫描识别,对应Bean根本未被创建 @PostConstruct执行时机过早,此时EntityManagerFactory Bean还未完成初始化流程,注入得到的实例为null
修复步骤
- 补全两个数据源配置类的核心注解,明确Bean绑定关系
以users数据源(可设为主数据源)为例,配置类头部必须明确指定关联的Bean名称,避免Spring自动装配时混淆:
类中声明的Bean名称必须和上述注解中引用的名称完全一致,实体扫描包路径要精准到对应数据源的实体子包,不要写过宽的父包,避免和另一个数据源的扫描范围重叠:@Configuration @Transactional(transactionManager = "userTransactionManager") @EnableJpaRepositories( basePackages = "it.unict.spring.application.persistence.repository.user", entityManagerFactoryRef = "userEntityManagerFactory", transactionManagerRef = "userTransactionManager" )
data数据源的配置逻辑完全同理,所有Bean名称、扫描包路径、持久化单元名称都要和users数据源严格区分,不能重叠。@Bean(name = "userDataSource") @ConfigurationProperties(prefix = "spring.datasource.users") // 对应配置文件中users数据源的配置前缀 public DataSource userDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "userEntityManagerFactory") public LocalContainerEntityManagerFactoryBean userEntityManagerFactory( @Qualifier("userDataSource") DataSource dataSource, EntityManagerFactoryBuilder builder ) { return builder .dataSource(dataSource) .packages("it.unict.spring.application.persistence.model.user") // 精准指向Privilege所在的实体包 .persistenceUnit("userPersistenceUnit") // 持久化单元名称全局唯一,和data数据源做区分 .build(); } @Bean(name = "userTransactionManager") public JpaTransactionManager userTransactionManager( @Qualifier("userEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory ) { return new JpaTransactionManager(entityManagerFactory.getObject()); } - 调整初始化逻辑的执行时机
不要在普通组件的@PostConstruct方法中写持久化逻辑,这个阶段Spring容器的Bean可能还未完成全部初始化流程,很容易拿到未就绪的实例。改用ApplicationRunner接口实现初始化逻辑,此时所有Bean已经完成实例化和属性注入,不会出现空指针问题:@Component public class PrivilegeInitRunner implements ApplicationRunner { private final PrivilegeRepository privilegeRepository; // 构造注入不需要额外加@Qualifier,只要Repository所在包和对应数据源的扫描路径匹配就能正确注入 public PrivilegeInitRunner(PrivilegeRepository privilegeRepository) { this.privilegeRepository = privilegeRepository; } @Override public void run(ApplicationArguments args) { // 写入Privilege初始化数据 privilegeRepository.save(new Privilege("ADMIN")); } } - 排除默认JPA自动配置的干扰
多数据源场景下要关闭SpringBoot默认的JPA、数据源自动配置,避免自动生成的Bean覆盖手动配置的多数据源实例,在启动类上添加排除配置即可:@SpringBootApplication(exclude = { HibernateJpaAutoConfiguration.class, DataSourceAutoConfiguration.class }) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
配置完成后启动,可通过控制台Hibernate的映射日志确认两个持久化单元都正确识别了各自的实体类,再执行数据操作就不会出现未知实体、插入null值、Bean注入空指针的问题。
内容的提问来源于stack exchange,提问作者Discipulos
相关产品推荐
相关产品推荐

