使用 @IdClass 的 JPA 多对多与额外列在 springTestContextPreparation Hibernate AnnotationException“没有持久的 id 属性”中失

Posted

技术标签:

【中文标题】使用 @IdClass 的 JPA 多对多与额外列在 springTestContextPreparation Hibernate AnnotationException“没有持久的 id 属性”中失败【英文标题】:JPA many-to-many with extra columns using @IdClass fails in springTestContextPreparation Hibernate AnnotationException "has no persistent id property" 【发布时间】:2019-06-21 16:15:38 【问题描述】:

我正在尝试使用 @IdClass 注释表示与某些实体的双向关系,如以下答案所示:https://***.com/a/32920550/8977519

我有一个完全遵循链接答案的解决方案设置,并且应用程序运行良好。我可以使用我所有的 CRUD 端点,并且更改会反映在数据库中。但是,当我尝试执行测试时,springTestContextPrepareTestInstance 步骤失败。

复合 Id 类

@Getter
@Setter
@EqualsAndHashCode
public class CompositeId implements Serializable 
    private static final long serialVersionUID = ...;

    @Type(type = "uuid-char")
    private UUID entity1;
    @Type(type = "uuid-char")
    private UUID entity2;

双向实体类

@Entity
@Table(name = "bidirectional_entity")
@NoArgsConstructor
@Getter
@Setter
@IdClass(CompositeId.class)
public class BidirectionalEntity 
    @Id
    @ManyToOne
    @JoinColumn(name = "entity1_id", referencedColumnName = "id")
    private Entity1 entity1;

    @Id
    @ManyToOne
    @JoinColumn(name = "entity2_id", referencedColumnName = "id")
    private Entity2 entity2;
    ...

实体类

@Entity
@Table(name = "entity1")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Entity1 
    @Id
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @GeneratedValue(generator = "uuid2")
    @Column(name = "id", updatable = false)
    @Type(type = "uuid-char")
    private UUID id;

    @OneToMany(mappedBy = "entity1",  orphanRemoval=true, cascade = CascadeType.ALL)
    @Setter(AccessLevel.NONE)
    private List<BidirectionalEntity> entities = new ArrayList<>();
    ...


@Entity
@Table(name = "entity2")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class Entity2 
    @Id
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @GeneratedValue(generator = "uuid2")
    @Column(name = "id", updatable = false)
    @Type(type = "uuid-char")
    private UUID id;

    @OneToMany(mappedBy = "entity2",  orphanRemoval=true, cascade = CascadeType.ALL)
    @Setter(AccessLevel.NONE)
    private List<BidirectionalEntity> entities = new ArrayList<>();
    ...

给出的错误是:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: CompositeId Class has no persistent id property
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
at org.springframework.test.context.testng.AbstractTestNGSpringContextTests.springTestContextPrepareTestInstance(AbstractTestNGSpringContextTests.java:145)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
at org.testng.internal.MethodInvocationHelper.invokeMethodConsideringTimeout(MethodInvocationHelper.java:59)
at org.testng.internal.Invoker.invokeConfigurationMethod(Invoker.java:458)
at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:222)
at org.testng.internal.Invoker.invokeConfigurations(Invoker.java:142)
at org.testng.internal.TestMethodWorker.invokeBeforeClassMethods(TestMethodWorker.java:168)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:105)
at org.testng.TestRunner.privateRun(TestRunner.java:648)
at org.testng.TestRunner.run(TestRunner.java:505)
at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
at org.testng.SuiteRunner.run(SuiteRunner.java:364)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
at org.testng.TestNG.runSuites(TestNG.java:1049)
at org.testng.TestNG.run(TestNG.java:1017)
at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:73)
at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)
Caused by: org.hibernate.AnnotationException: CompositeId Class has no persistent id property
at org.hibernate.cfg.AnnotationBinder.bindIdClass(AnnotationBinder.java:2858)
at org.hibernate.cfg.AnnotationBinder.mapAsIdClass(AnnotationBinder.java:1053)
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:781)
at org.hibernate.boot.model.source.internal.annotations.AnnotationMetadataSourceProcessorImpl.processEntityHierarchies(AnnotationMetadataSourceProcessorImpl.java:250)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess$1.processEntityHierarchies(MetadataBuildingProcess.java:231)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:274)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774)
... 45 more

更新

在做了一些新手调试之后,在 Hibernate Core (5.3.9) AnnotationBinder 类 -> bindIdClass 方法中,componentId 对象 properties 列表在执行测试设置时为空(显然,它是什么检查抛出错误)。但是正常运行程序时,有valueorg.hibernate.mapping.SimpleValue([org.hibernate.mapping.Column(entity*_id)])nameentity*这两个属性。与应有的 BidirectionalEntity 类匹配。

【问题讨论】:

你能发布你的@Entity 和@Test 的代码吗? @GabrielPimenta 我添加了一些代码 sn-ps。这些测试并没有什么特别之处,它们使用@SpringBootTest@AutoconfigureMockMvc 并扩展AbstractTestNGSpringContextTests。实际的测试用例代码似乎不相关,因为在尝试执行任何设置之前设置失败。 【参考方案1】:

我已经解决了这个问题。除了我的服务测试,我还在编写测试来验证 POJO 上的约束验证注释。我忘记在 CompositeId 测试类名称 (CompositeIdTest) 的末尾添加“测试”。似乎在春季测试上下文中检查测试类被拉入并由 Hibernate 检查 ID 属性。一旦我更改了测试类名称,测试就可以正常运行了。

【讨论】:

以上是关于使用 @IdClass 的 JPA 多对多与额外列在 springTestContextPreparation Hibernate AnnotationException“没有持久的 id 属性”中失的主要内容,如果未能解决你的问题,请参考以下文章

带有额外列的 JPA 2.0 多对多 - 更新集合

Spring boot JPA - 使用额外的列查询多对多

流利的 NHibernate 多对多与额外的列不插入

具有额外多对多关系的 JPA 多对多

一对一与多对多与重复条目

openjpa:多对多,带有额外的列