2

I am looking for a way to model a relation between two or more objects of the same class in Hibernate. For example: I want to create a situation where I want to model relations between persons. In the database, I have two tables:

Person:

  • Id
  • Name

Relation:

  • Parent Id
  • Child Id

I tried to model this in Hibernate as a Person have two ManyToMany relations (Getter/Setter annotations are Lombok):

@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "persons")
public class Person {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long Id

  @Column(name="name")
  private String name;


 @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(
      name = "relations",
      joinColumns = {@JoinColumn(name = "parent_id", nullable = false)},
      inverseJoinColumns = {@JoinColumn(name = "child_id", nullable = false)}
  )
  private Set<Person> children = new HashSet<>();

  @ManyToMany(fetch = FetchType.LAZY)
  @JoinTable(
      name = "relations",
      joinColumns = {@JoinColumn(name = "child_id", nullable = false)},
      inverseJoinColumns = {@JoinColumn(name = "parent_id", nullable = false)}
  )
  private Set<Person> parents = new HashSet<>();
}

This gave me the following problems:

  • With fetch type "LAZY" Hibernate complains about not having a Session when calling person.getChildren() or person.getParents()
  • With fetch type "EAGER" Hibernate returns null for the sets, which causes nullpointers when trying to add children or parents. The other thing I am worried about is that possible endless recursive eager fetching.

To get around this, I've tried to model the Relation as a class, so that I can use JPA queries in the PersonRepository to find Children and Parents without having to mess with the intricacies of ManyToMany :

public interface PersonRepository extends JpaRepository<Person, Long> {
  @Query(
      "select p from Person p join Relation r on p.id = r.childId where r.childId = :childId"
  )
  List<Person> findParents(Long childId);
}

This caused Hibernate to complain that Relation does not have an @Id field. I do not want to add that because the relation table in the database should model the relation, and not have an id of its own.

When trying to search online for similar structures in Hibernate I usually find classic many-to-many examples in the documentation and questions like this one where there is a relation between two different classes instead of a relation between two objects of the same class.

I'm looking for a way to model a relation between two or more objects of the same class in Hibernate. I want to be able to fetch relations lazily, or fetch them afterwards for performance reasons.

It would be nice if the relation table could have an extra field "type" which indicates the type of relation between Persons (child, parent, nephew, friend) so that there is room for new relation types without too much changes to database and code.

Rolf
  • 7,098
  • 5
  • 38
  • 55
  • when it comes to using the type field in your linking table then maybe my answer here would be of benefit: https://stackoverflow.com/questions/41732211/mapping-many-to-many-relationship-with-attributes-with-jpa/41732504#41732504 – Maciej Kowalski Sep 26 '17 at 09:51
  • Thank you, but that maps a manytomany relation between two different classes. I'm trying to map Persons to Persons. – Rolf Sep 26 '17 at 09:58

1 Answers1

0

Not sure I understand you correctly, but I had a similar case once. What I did was to persist the child/parent without its relations and updated them with their relations afterwards (still in the same transaction).

 private void insertEntity(final AbstractEntity entity) {
    insertedEntities.add(entity.getId());

    final List<AbstractEntity> relations = entity.onBeforeInsertion();
    for (final AbstractEntity relation : relations) {
        if (!insertedEntities.contains(relation.getId())) {
            insertEntity(relation);
        }
    }

    final RelationBundle relationBundle = new RelationBundle();
    entity.onInsertion(relationBundle);

    immediatelySaveNewEntityThroughProxy(entity);

    for (final AbstractEntity relation : relations) {
        entity.onRelationInsertion(relation, relationBundle);
    }

    dao.saveOrUpdate(entity);
}


private void immediatelySaveNewEntityThroughProxy(final DocumentEntity entity) {
    proxiedAccess().immediatelySaveNewEntity(entity);
}

private MyConsumer proxiedAccess() {
    return applicationContext.getBean(getClass());
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void immediatelySaveNewEntity(final DocumentEntity entity) {
    try {
        if (!dao.entityExistsFromId((int) entity.getId())) {
            dao.save(entity);
        }
    } catch (final Exception e) {
        LOGGER.error("Error saving entity: {}", entity.getId(), e);
    }
}
sschrass
  • 7,014
  • 6
  • 43
  • 62
  • I tried doing that, but Person.getChildren returns null, which according to my understanding should not happen. What can I do to clearify the question? – Rolf Sep 26 '17 at 09:53
  • this only could happen if updating fails somehow. If you are using Spring, you may need a new Proxy instance. ref: http://stackoverflow.com/questions/15767914/strange-behaviour-with-transactionalpropagation-propagation-requires-new and http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/htmlsingle/#tx-decl-explained – sschrass Sep 26 '17 at 11:27
  • updated answer with some code. The events like `onBeforeXXX` just remove relations and save them to a Bundle. – sschrass Sep 26 '17 at 11:33
  • I resorted to modellig the relation as a separate relation with an Id, and two Longs as parent and child id's. The project is in a hurry. The Relation repository has methof to fetch parent Ids based on child id, and I use those Ids to fetch the actual person objects. Suboptimal but does not impact performance with eager fetching, and doesn't cause problems with expired sessions. I'm really tempted to migrate the project to MyBatis. – Rolf Sep 27 '17 at 15:03