简体   繁体   中英

How to set up a child entity referencing parent by only ID using a JoinTable in Spring JPA/Hibernate

Problem

  1. I have a parent entity with a one-to-many relationship with child entities.
  2. Mapstruct is being used to map entities to DTOs, so just fetching all (even lazily) will result in every instance being instantiated/fetched from persistence (which we do not want, I only care about IDs outside of the Spring JPA/Hibernate classes)
  3. Both parent and child's tables have no reference of the other, the relationship is handled by a join table

I only care about the IDs for the associations. I do not want the DTO to have the respective associated class as an attribute. I want Parent to have Set<String> childIds and Child to have String parentId in the DTOs. Because of this, I do not want to load the entire object in the persistence layer just to map away everything else.

Attempts

  1. Having the entity classes reference the other entity class. Entity → DTO is ok , but DTO → Entity Hibernate/Spring JPA complains that the entity is detached (because it just came from an ID). One solution I thought of is calling EntityManager.getReference but that throws an error if it's a new entity, so how can I save new ones? I'd have to do an existsById but now we're making even more database calls, this is getting expensive
  2. A bunch of combinations of JoinColumn , JoinColumns , JoinTable , etc.

Java classes

Entities

@Entity
@(LombokGettersAndSetters)
public class ParentEntity {
    
    @Id
    private Long id;
    
    @(???)
    private Set<Long> childIds; || private Set<ChildEntity> children;
}
@Entity
@(LombokGettersAndSetters)
public class ChildEntity {
    
    @Id
    private Long id;

    @(???)
    private Long parentId; || private ParentEntity parent;
}

Mappers

(Mapstruct mappers)

DTOs

public class ParentDTO {
    private Long id;
    private Set<Long> childIds;
}
public class ChildDTO {
    private Long id;
    private Long parentId;
}

Tables

Parent       Child      Parent_join_Child
-------      -----      -----------------
id           id         parent_id
                        child_id

Thanks in advance!

Edit @ +1m : I should note, I removed some annotations in my examples such as the below

@Column(name = "parent_id", columnDefinition = CustomColumnDefinition.UNSIGNED_INT)
@GeneratedValue(strategy = GenerationType.IDENTITY)

You can use @JoinTable to join your parent to child as shown below

@Entity
@(LombokGettersAndSetters)
public class ParentEntity {
    
    @Id
    private Long id;
    
    @JoinTable(name = "Parent_join_Child", joinColumns = {
            @JoinColumn(name = "parent_id", referencedColumnName = "id") }, inverseJoinColumns = {
                    @JoinColumn(name = "child_id", referencedColumnName = "id") })
    private Set<ChildEntity> children;
}

Can you do the following:

Create ParentChild entity:

@Entity
public class ParentChild {
  
  private long id;
  private long parentId;
  private long childId;

}

and work with it:

   parentChildRepository.findByWhatever() and group by parent and map to dto.

You'd have to write a custom query that selects only the parts that you care about, but then you won't be able to use Mapstruct. I recommend you take a look at whatBlaze-Persistence Entity Views has to offer.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

Assuming an entity model like this:

@Entity
public class ParentEntity {
    
    @Id
    private Long id;
    
    @OneToMany(mappedBy = "parent")
    private Set<ChildEntity> children;
}

@Entity
public class ChildEntity {
    
    @Id
    private Long id;

    @ManyToOne
    private ParentEntity parent;
}

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(ParentEntity.class)
public interface ParentDTO {
    @IdMapping
    Long getId();
    @Mapping("children.id")
    Set<Long> getChildIds();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

ParentDTO a = entityViewManager.find(entityManager, ParentDTO.class, id);

Behind the scenes this will create the query to fetch exactly what is needed, nothing more.

The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

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