Froejo
Froejo

Reputation: 71

Mapping same JPA-entity to other JPA-entity multiple times

I want to map the User to Relationship, through first_user and second_user, so that I can keep track of their relationship statuses. There might be a better approach and I'll gladly consider any helpful suggestions.

The main problem arrives when i try to persist. The "relationship"-side of the relationship seems to be working just fine when I check the database but the table user_relationship is not working.

So the question is basically why isn't isn't the persisting of relationships in user working?

Edit: I've seen the post which this has been suggested a duplicate of. In the accepted answer Sym-sym points to fixing the relationship and provides examples. I've looked into the examples but the only difference I see on the OneToMany-side of the relationship is the mappedBy attribute. I'm not really sure which one to map by since we have two ManyToOne on the other side of the relationship. Would we map by firstParty or perhaps secondParty and what happens to the other one?

This is what I got so far:

@Entity
@Table(name = "user")
data class User(

    @Id
    @GeneratedValue(generator = "my_user_sequence")
    @SequenceGenerator(name = "my_user_sequence", sequenceName = "user_sequence", allocationSize = 1)
    val id: Long = -1,

    @JsonIgnore
    @OneToOne(mappedBy = "user", cascade = [CascadeType.ALL], optional = false)
    var preferences: Preferences? = null,

    @OneToMany(cascade = [CascadeType.ALL])
    var relationships: MutableSet<Relationship> = mutableSetOf(),

    @Column(name = "facebook_id")
    var fbId: String = "",
    var email: String? = null,

    var registrationToken: String = "",

    var birthday: LocalDate? = null,

    val created: LocalDateTime = LocalDateTime.now(Clock.systemUTC()),

    @Column(name = "phone_number")
    var phoneNumber: String = "",
    var password: String? = null,

    @Column(name = "first_name")
    var firstName: String? = null,

    @Column(name = "show_location")
    var showLocation: Boolean = true,

    @Column(name = "show_age")
    var showAge: Boolean = true,

    @Column(name = "picture_url")
    var pictureUrl: String? = null,

    @Column(name = "notify_message")
    var notifyMessage: Boolean = true,

    @Column(name = "notify_relationships")
    var notifyRelationship: Boolean = true
)

This is the relationship class:

@Entity
@Table(name = "relationship")
data class Relationship(

        @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "joyship_relationship_sequence")
    @SequenceGenerator(name = "joyship_relationship_sequence", sequenceName = "relationship_sequence", allocationSize = 1)
    val id: Long = -1,

    @ManyToOne(cascade = [])
    @JoinTable(name = "first_party")
    val firstParty: User,

    @ManyToOne(cascade = [])
    @JoinTable(name = "second_party")
    val secondParty: User,

    // Id of user is key for relationship_status of user with smaller number on id
    @Enumerated(EnumType.STRING)
    @Column(name = "first_status")
    var firstStatus: RelationshipStatus = NOT_INTRODUCED,

    // Id of user is key for relationship_status of user with bigger number on id
    @Enumerated(EnumType.STRING)
    @Column(name = "second_status")
    var secondStatus: RelationshipStatus = NOT_INTRODUCED,

    // Changes when relationship_status_1 or *_2 changes
    // (Strongest of the statuses dictates common_relationship_status)
    @Enumerated(EnumType.STRING)
    @Column(name = "common_relationship_status")
    var commonRelationshipStatus: RelationshipStatus = NOT_INTRODUCED,

    @Column(name = "relative_distance")
    var relativeDistance: Int = 160,

    @Column
    val established: LocalDateTime = LocalDateTime.now(Clock.systemUTC()),

    @OneToOne(mappedBy = "relationship", cascade = [CascadeType.ALL], optional = false)
    @JoinColumn(name = "last_interaction")
    var lastInteraction: Interaction? = null,

    @Column
    var lastInteractionTime: LocalDateTime = LocalDateTime.now(Clock.systemUTC()))

It consistently gives me this when i try to persist a user with relationships:

    javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: co.joyship.server.fraendurheim.models.Relationship

    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:149)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:164)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:814)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:774)
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
    at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:467)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:392)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
    at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:500)
    at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:432)
    at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:395)
    at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:193)
    at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:126)
    at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:445)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:281)
    at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182)
    at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125)
    at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
    at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:783)
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:768)
    at org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager.persist(TestEntityManager.java:93)
    at co.joyship.server.fraendurheim.RelationshipRepositoryTest.whenRequestingUserRelationshipFromDBReturnCorrectPagination(RelationshipRepositoryTest.kt:64)
    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.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    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:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    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:190)
    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:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: co.joyship.server.fraendurheim.models.Relationship
    at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124)
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:807)
    ... 52 more

Upvotes: 2

Views: 1466

Answers (1)

Froejo
Froejo

Reputation: 71

If anyone ever finds themselves in the same situation as I was: Don't try this approach. It is not possible, as far as my trials show, to create a bi-directional relationship between two @ManyToOne in one entity and just one @OneToMany in the other entity.

The two solutions found for our specific case so far is either to:

  • Map two separate fields in each of the entities to each other. Not just one field in the one object to two fields in the other object.
  • Or, map the objects with one field @ManyToMany and figure out another way to determine who owns which status.(Like largest ID or something like that)

I used this reference to get the @OneToMany relationships correct

Upvotes: 1

Related Questions