简体   繁体   中英

Self-referencing ManyToMany relations in Hibernate

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.

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);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM