
本教程详细探讨了在Spring Boot应用中配置多个JPA数据源时,原生查询可能遇到的指向错误问题。当配置了主次两个PostgreSQL数据库,且在次要数据库上执行JPA原生SQL查询时,系统可能错误地将查询路由到主数据库。核心解决方案在于利用@PersistenceContext注解的unitName属性,结合LocalContainerEntityManagerFactoryBean的setPersistenceUnitName方法,明确指定EntityManager与特定持久化单元的关联,从而确保原生查询正确作用于目标数据库。
在spring boot应用程序中管理多个数据源是一项常见的需求,尤其是在需要与不同业务领域或遗留系统交互的复杂场景中。jpa(java persistence api)通过hibernate等实现,为对象关系映射提供了强大的支持。然而,当涉及到多个数据源,并且需要执行原生sql查询时,可能会遇到一些挑战,例如查询错误地指向了错误的数据源。
问题描述
假设一个Spring Boot应用配置了两个PostgreSQL数据库:powwow(主数据库)和pims(次要数据库)。应用程序能够成功地连接到这两个数据库,并独立地执行事务。对于次要数据库pims,通过JPA Repository执行非原生查询时一切正常。但是,当尝试在pims数据库上执行原生SQL查询(例如查询merchants表)时,却收到PSQLException: ERROR: relation “merchants” does not exist的错误。进一步的测试表明,相同的原生查询如果针对powwow数据库中的表,则可以正常执行。这表明问题出在次要数据源pims的配置上,导致原生查询没有正确地路由到pims数据库。
问题的核心在于,当存在多个EntityManagerFactory实例时,Spring需要明确知道@PersistenceContext注解应该注入哪一个EntityManager。如果未明确指定,Spring可能会选择默认的或主EntityManager,导致原生查询在错误的数据库上下文中执行。
解决方案:使用 PersistenceUnitName
解决此问题的关键在于为每个EntityManagerFactory明确指定一个唯一的PersistenceUnitName,并在需要注入特定EntityManager的地方通过@PersistenceContext注解引用这个名称。
PersistenceUnitName是一个逻辑名称,用于标识一个持久化单元,它封装了一组实体类及其到特定数据源的映射。通过为每个数据源的EntityManagerFactory设置一个独特的PersistenceUnitName,并让DAO层在注入EntityManager时明确指定所需的PersistenceUnitName,可以消除Spring在选择EntityManager时的歧义。
配置次要数据源的持久化单元名称
首先,在次要数据源(例如pims)的JPA配置类中,为LocalContainerEntityManagerFactoryBean设置一个唯一的PersistenceUnitName。
PersistencePimsAutoConfiguration.java 更新示例:
蓝心千询
蓝心千询是vivo推出的一个多功能AI智能助手
34 查看详情
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.beans.factory.annotation.Value;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;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 javax.sql.DataSource;import java.net.InetAddress;import java.net.UnknownHostException;import java.util.HashMap;@Configuration@PropertySource({"classpath:application.properties"})@EnableJpaRepositories( basePackages = {"com.xxxx.powwow.dao.pims", "com.xxxx.powwow.repositories.pims"}, entityManagerFactoryRef = "pimsEntityManager", transactionManagerRef = "pimsTransactionManager")public class PersistencePimsAutoConfiguration { private Logger logger = LogManager.getLogger(PersistencePimsAutoConfiguration.class); @Value("${spring.datasource1.jdbc-url}") private String url; @Value("${spring.datasource1.username}") private String username; @Value("${spring.jpa.hibernate.ddl-auto}") private String hbm2ddl; @Value("${spring.jpa.database-platform}") private String platform; @Value("${spring.jpa.properties.hibernate.dialect}") private String dialect; @Value("${spring.profiles.active}") private String profile; @Bean @ConfigurationProperties(prefix="spring.datasource1") public DataSource pimsDataSource() { return DataSourceBuilder.create().build(); } @Bean public LocalContainerEntityManagerFactoryBean pimsEntityManager() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(pimsDataSource()); em.setPackagesToScan(new String[] {"com.xxxx.powwow.entities.pims"}); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); HashMap properties = new HashMap(); properties.put("hibernate.hbm2ddl.auto", hbm2ddl); properties.put("hibernate.dialect", dialect); em.setJpaPropertyMap(properties); // **新增行:设置持久化单元名称** em.setPersistenceUnitName("pimsPersistenceUnit"); // 确保此名称在整个应用中唯一 String host = null; try { host = InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { throw new RuntimeException(e); } logger.info("Setting spring.datasource1 (pims): hibernate.hbm2ddl.auto='"+hbm2ddl+"', platform='"+platform+"', url='"+url+"', username='"+username+"', host='"+host+"', profile='"+profile+"'."); return em; } @Bean public PlatformTransactionManager pimsTransactionManager() { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(pimsEntityManager().getObject()); return transactionManager; }}
在DAO层引用指定的持久化单元
接着,在需要使用次要数据源的EntityManager的DAO类中,通过@PersistenceContext注解的unitName属性引用上一步设置的持久化单元名称。
BookingHistoryReportDao.java 更新示例:
import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;import javax.persistence.EntityManager;import javax.persistence.PersistenceContext;import javax.persistence.Query;import java.util.Date;import java.util.List;@Component@Transactional("pimsTransactionManager")public class BookingHistoryReportDao { private Logger logger = LogManager.getLogger(BookingHistoryReportDao.class); // **修改行:指定持久化单元名称** @PersistenceContext(unitName = "pimsPersistenceUnit") private EntityManager entityManager; public void executeBookingHistoryReport(Date startDate, Date endDate, List companyIds) { final String sql = getSQLBookingHistoryReportDao(); try { Query qry = entityManager.createNativeQuery(sql); List merchants = qry.getResultList(); logger.info("done"); } catch (Exception e) { logger.error("Error executing query for BookingHistoryReport.", e); logger.info(sql); } } private String getSQLBookingHistoryReportDao() { return "select company_name from Merchants limit 100"; }}
工作原理
当Spring容器启动并扫描到多个LocalContainerEntityManagerFactoryBean(即多个EntityManagerFactory)时,它需要一种机制来区分它们。
em.setPersistenceUnitName(“pimsPersistenceUnit”): 这行代码为pimsEntityManager这个EntityManagerFactory实例赋予了一个逻辑名称pimsPersistenceUnit。这个名称在Spring的内部注册表中是唯一的,用于标识这个特定的持久化单元。@PersistenceContext(unitName = “pimsPersistenceUnit”): 当Spring尝试将EntityManager注入到BookingHistoryReportDao时,unitName属性明确告诉Spring,它应该注入与名为pimsPersistenceUnit的持久化单元相关联的EntityManager。这样,entityManager实例就保证了与pims数据库的连接和事务上下文相关联。原生查询的正确路由: EntityManager.createNativeQuery()方法会使用其所属持久化单元的底层数据库连接来执行SQL。通过明确指定unitName,确保了entityManager实例指向pims数据库,因此原生查询select company_name from Merchants将会在pims数据库上执行,而不是错误地路由到powwow数据库。
注意事项与最佳实践
唯一性: 确保每个LocalContainerEntityManagerFactoryBean的PersistenceUnitName都是唯一的。一致性: 对于每个数据源,其相关的@EnableJpaRepositories注解应通过entityManagerFactoryRef和transactionManagerRef明确引用对应的EntityManagerFactory和PlatformTransactionManager Bean。主数据源: 如果存在一个“主”数据源,并且其EntityManagerFactory没有明确设置PersistenceUnitName,或者在@PersistenceContext中没有指定unitName,Spring可能会将其视为默认的EntityManager。因此,即使是主数据源,也建议为其设置PersistenceUnitName以避免潜在的歧义。事务管理: 确保JpaTransactionManager正确地关联到其对应的EntityManagerFactory。本例中pimsTransactionManager通过transactionManager.setEntityManagerFactory(pimsEntityManager().getObject())正确地完成了关联。实体包扫描: em.setPackagesToScan()应指向该数据源对应的实体类包,确保实体与正确的数据库映射。
总结
在Spring Boot应用中配置多个JPA数据源时,尤其是涉及到原生SQL查询时,明确的配置至关重要。通过为每个LocalContainerEntityManagerFactoryBean设置唯一的PersistenceUnitName,并在DAO层通过@PersistenceContext(unitName = “…”)引用它,可以有效地解决原生查询指向错误数据库的问题,确保应用程序的稳定性和正确性。这种显式配置的方法增强了代码的可读性和可维护性,是多数据源JPA配置中的一个重要实践。
以上就是Spring Boot多数据库JPA原生查询配置指南的详细内容,更多请关注创想鸟其它相关文章!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/232095.html
微信扫一扫
支付宝扫一扫