Issue
Say I have two tables, in different schemas. Therefore, I create an entity and transaction manager for each data source:
@Configuration
@EnableJpaRepositories(
basePackages = "com.(omitted).endpoints.all.endpoint.crm",
entityManagerFactoryRef = "crmEntityManager",
transactionManagerRef = "crmTransactionManager"
)
public class PersistenceCrmConfig {
@Autowired
private Environment environment;
public PersistenceCrmConfig() {
super();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean crmEntityManager() {
final LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(crmDataSource());
bean.setPackagesToScan("com.(omitted).data.all.model.crm");
...
}
@Primary
@Bean
public PlatformTransactionManager crmTransactionManager() {
...
}
@Primary
@Bean
public DataSource crmDataSource() {
...
}
}
@Configuration
@EnableJpaRepositories(
basePackages = "com.(omitted).endpoints.all.endpoint.user",
entityManagerFactoryRef = "userEntityManager",
transactionManagerRef = "userTransactionManager"
)
public class PersistenceUserConfig {
@Autowired
private Environment environment;
public PersistenceUserConfig() {
super();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean userEntityManager() {
final LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(userDataSource());
bean.setPackagesToScan("com.(omitted).data.all.model.user");
...
}
@Primary
@Bean
public PlatformTransactionManager userTransactionManager() {
...
}
@Primary
@Bean
public DataSource userDataSource() {
...
}
}
In the user
data source (schema), I have the entity, User
, that has a one-to-one mapping to the entity, Person
, in the crm
data source (schema).
@JoinColumn(name = "person_id", nullable = false)
@OneToOne(fetch = FetchType.LAZY)
private Person person;
This causes an exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userEntityManager' defined in class path resource [com/(omitted)/endpoints/all/spring/PersistenceUserConfig.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: @OneToOne or @ManyToOne on com.(omitted).data.all.model.user.User.person references an unknown entity: com.(omitted).data.all.model.crm.Person
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1786) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:602) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154) ~[spring-context-5.3.8.jar:5.3.8]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908) ~[spring-context-5.3.8.jar:5.3.8]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.8.jar:5.3.8]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.1.jar:2.5.1]
at org.springframework.boot.builder.SpringApplicationBuilder.run(SpringApplicationBuilder.java:144) ~[spring-boot-2.5.1.jar:2.5.1]
at com.(omitted).endpoints.all.Application.main(Application.java:11) ~[classes/:na]
Caused by: org.hibernate.AnnotationException: @OneToOne or @ManyToOne on com.(omitted).data.all.model.user.User.person references an unknown entity: com.(omitted).data.all.model.crm.Person
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:100) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processEndOfQueue(InFlightMetadataCollectorImpl.java:1823) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processFkSecondPassesInOrder(InFlightMetadataCollectorImpl.java:1767) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1655) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:295) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1224) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1255) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.3.8.jar:5.3.8]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.3.8.jar:5.3.8]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-5.3.8.jar:5.3.8]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-5.3.8.jar:5.3.8]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1845) ~[spring-beans-5.3.8.jar:5.3.8]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1782) ~[spring-beans-5.3.8.jar:5.3.8]
... 15 common frames omitted
I understand why I get this error. Spring must generate two Hibernate Session Factories and therefore, two lists of entities that know nothing about each other.
How can I get the one-to-one mapping to work?
Thank you.
Solution
There is no way to make it work with two Hibernate Session Factories. I also would not recommend trying to force Hibernate to create a single one for this case.
Easies workaround I see is on the DB side.
Consider creating a view of person
table in your user
schema and map your Person
entity to the view, not to the table.
This approach will not allow you to update the person
table using your userEntityManager
(unless you are using Oracle DB). If you need to do it at some point, you can use your crmEntityManager
to find the corresponding person
in the table and update it.
Keep in mind, that transactionality may be harmed if you have to update both person
and user
as a part of a single operation due to the same reason you mentioned (different Session Factories).
Answered By - Alexandr Ter