带有额外列的 Spring Data JPA 多对多
Posted
技术标签:
【中文标题】带有额外列的 Spring Data JPA 多对多【英文标题】:Spring Data JPA Many to Many with extra column 【发布时间】:2017-10-17 12:14:00 【问题描述】:我正在尝试在两个表之间建立多对多关系。该关系包含额外信息(日期)。我正在尝试使用 Spring Data JPA 来实现这一目标,并且正在使用单元测试进行测试。测试失败并给出错误:
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find ....JoinEntity with id ....JoinEntity@5934ca1e; nested exception is javax.persistence.EntityNotFoundException: Unable to find ....JoinEntity with id ....JoinEntity@5934ca1e
这是我的代码:
实体 A:
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
@Entity
@Table(name= "tableA")
public class EntityA
/** The primary key. */
@Id
private Integer id;
private String name;
@OneToMany(mappedBy="a")
private Set<JoinEntity> bs;
//... getters and setters
实体 B:
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name= "tableB")
public class EntityB
/** The primary key. */
@Id
private Integer id;
private String name;
//... getters and setters
加入实体:
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@Table(name="joinTableAB")
@IdClass(ABId.class)
public class JoinEntity implements Serializable
@Column(name = "join_date")
private Date date;
@Id
@ManyToOne
@JoinColumn(name = "a_id")
private EntityA a;
@Id
@ManyToOne
@JoinColumn(name = "b_id")
private EntityB b;
//... getters and setters
A 的存储库:
import org.springframework.data.repository.CrudRepository;
public interface ARepository extends CrudRepository<EntityA,Integer>
B 的存储库:
import org.springframework.data.repository.CrudRepository;
public interface BRepository extends CrudRepository<EntityB,Integer>
简单的测试类:
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Testing for AtmosphericConditionsRepository.
*/
@DataJpaTest
@RunWith(SpringRunner.class)
@SpringBootTest(classes = ARepositoryTest.class)
public class ARepositoryTest
@Autowired
ARepository aRepository;
@Autowired
BRepository bRepository;
@Test
public void test()
EntityA a0 = new EntityA();
a0.setId(0);
a0.setName("a0");
EntityA a1 = new EntityA();
a1.setId(1);
a1.setName("a1");
a0 = aRepository.save(a0);
a1 = aRepository.save(a1);
EntityB b0 = new EntityB();
b0.setId(0);
b0.setName("b0");
EntityB b1 = new EntityB();
b1.setId(1);
b1.setName("b1");
b0 = bRepository.save(b0);
b1 = bRepository.save(b1);
Set<JoinEntity> joinEntities = new HashSet<>();
JoinEntity je = new JoinEntity();
je.setDate(new Date());
je.setA(a0);
je.setB(b0);
joinEntities.add(je);
a0.setBs(joinEntities);
aRepository.save(a0);
@IdClass 中指定的类:
import java.io.Serializable;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
public class ABId implements Serializable
private EntityA a;
private EntityB b;
public ABId()
public ABId(EntityA pA, EntityB pB)
a = pA;
b = pB;
public EntityA getA()
return a;
public EntityB getB()
return b;
@Override
public boolean equals(Object pO)
if (this == pO)
return true;
if (pO == null || getClass() != pO.getClass())
return false;
ABId abId = (ABId) pO;
if (a != null ? !a.equals(abId.a) : abId.a != null)
return false;
return b != null ? b.equals(abId.b) : abId.b == null;
@Override
public int hashCode()
int result = a != null ? a.hashCode() : 0;
result = 31 * result + (b != null ? b.hashCode() : 0);
return result;
扩展堆栈跟踪:
2017-10-18 08:30:48.696 INFO 6460 --- [ main] o.s.t.c.transaction.TransactionContext : Began transaction (1) for test context [DefaultTestContext@de3a06f testClass = ARepositoryTest, testInstance = ....ARepositoryTest@58a90037, testMethod = test@ARepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@76b10754 testClass = ARepositoryTest, locations = '', classes = 'interface ....ARepository, interface ....BRepository, class ....JoinEntity, class ....ABId, interface ....ARepository, interface ....BRepository, class ....JoinEntity, class ....ABId', contextInitializerClasses = '[]', activeProfiles = '', propertySourceLocations = '', propertySourceProperties = 'org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@4493d195, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@4e1d422d, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@5c8ff52f, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3cb1ffe6, org.springframework.boot.test.context.ImportsContextCustomizer@274bc460, org.springframework.boot.test.context.SpringBootTestContextCustomizer@2c039ac6, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6b57696f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@5e13f156]; rollback [true]
Hibernate: select entitya0_.id as id1_6_0_, entitya0_.name as name2_6_0_ from tablea entitya0_ where entitya0_.id=?
Hibernate: select entitya0_.id as id1_6_0_, entitya0_.name as name2_6_0_ from tablea entitya0_ where entitya0_.id=?
Hibernate: select entityb0_.id as id1_7_0_, entityb0_.name as name2_7_0_ from tableb entityb0_ where entityb0_.id=?
Hibernate: select entityb0_.id as id1_7_0_, entityb0_.name as name2_7_0_ from tableb entityb0_ where entityb0_.id=?
Hibernate: select joinentity0_.a_id as a_id2_2_0_, joinentity0_.b_id as b_id3_2_0_, joinentity0_.join_date as join_dat1_2_0_, entitya1_.id as id1_6_1_, entitya1_.name as name2_6_1_, entityb2_.id as id1_7_2_, entityb2_.name as name2_7_2_ from join_tableab joinentity0_ inner join tablea entitya1_ on joinentity0_.a_id=entitya1_.id inner join tableb entityb2_ on joinentity0_.b_id=entityb2_.id where joinentity0_.a_id=? and joinentity0_.b_id=?
2017-10-18 08:30:49.035 INFO 6460 --- [ main] o.s.t.c.transaction.TransactionContext : Rolled back transaction for test context [DefaultTestContext@de3a06f testClass = ARepositoryTest, testInstance = ....ARepositoryTest@58a90037, testMethod = test@ARepositoryTest, testException = org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find ....JoinEntity with id ....ABId@5a9ba131; nested exception is javax.persistence.EntityNotFoundException: Unable to find ....JoinEntity with id ....ABId@5a9ba131, mergedContextConfiguration = [MergedContextConfiguration@76b10754 testClass = ARepositoryTest, locations = '', classes = 'interface ....ARepository, interface ....BRepository, class ....JoinEntity, class ....ABId, interface ....ARepository, interface ....BRepository, class ....JoinEntity, class ....ABId', contextInitializerClasses = '[]', activeProfiles = '', propertySourceLocations = '', propertySourceProperties = 'org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@4493d195, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@4e1d422d, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@5c8ff52f, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3cb1ffe6, org.springframework.boot.test.context.ImportsContextCustomizer@274bc460, org.springframework.boot.test.context.SpringBootTestContextCustomizer@2c039ac6, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@6b57696f, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]].
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find ....JoinEntity with id ....ABId@d3513f0a; nested exception is javax.persistence.EntityNotFoundException: Unable to find ....JoinEntity with id ....ABId@d3513f0a
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:389)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:246)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:491)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy93.save(Unknown Source)
at ....ARepositoryTest.test(ARepositoryTest.java:60)
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.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Caused by: javax.persistence.EntityNotFoundException: Unable to find ....JoinEntity with id ....ABId@d3513f0a
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:144)
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
at org.hibernate.event.internal.DefaultLoadEventListener.proxyOrLoad(DefaultLoadEventListener.java:278)
at org.hibernate.event.internal.DefaultLoadEventListener.doOnLoad(DefaultLoadEventListener.java:121)
at org.hibernate.event.internal.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:89)
at org.hibernate.internal.SessionImpl.fireLoad(SessionImpl.java:1129)
at org.hibernate.internal.SessionImpl.internalLoad(SessionImpl.java:1022)
at org.hibernate.type.EntityType.resolveIdentifier(EntityType.java:639)
at org.hibernate.type.EntityType.resolve(EntityType.java:431)
at org.hibernate.type.EntityType.replace(EntityType.java:330)
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:518)
at org.hibernate.type.CollectionType.replace(CollectionType.java:663)
at org.hibernate.type.TypeHelper.replace(TypeHelper.java:177)
at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:401)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:203)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:176)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:69)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:840)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:822)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:827)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:1161)
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.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
at com.sun.proxy.$Proxy85.merge(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:509)
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.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:503)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:488)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:460)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
... 41 more
【问题讨论】:
“JoinEntity”的@IdClass
在哪里?因为你有不止 1 个@Id
,所以你需要一个
你确定它是强制性的吗?我正在使用 @Id 两次创建复合键。
我用@IdClass试过了,结果是一样的,只是消息稍有不同(当指出没有找到哪个id时)。还有其他想法吗?
你能显示更多的堆栈跟踪吗,请? (只是一个猜测:为 JoinEntity 写一个 Dao(不贵)并在你的测试中使用它。)
当然是必须的。如果你有一个复合键,那么如果你没有定义类型,你将没有任何东西可以输入到em.find(...)
!
【参考方案1】:
我错过了集合上的 Cascade 类型:/。
添加后:
@OneToMany(mappedBy="a",cascade = CascadeType.ALL)
private Set<JoinEntity> bs;
现在可以正常使用了。
【讨论】:
以上是关于带有额外列的 Spring Data JPA 多对多的主要内容,如果未能解决你的问题,请参考以下文章