How to eagerly initialize JPA entity collections from native query

Viewed 45

I have a case where I need to use a native query in JPA (I'm using Spring Data JPA) that involves joining with a collection of child entities (association is bi-directional @OneToMany) and I would like to initialize my parent entity with the child entities (i.e., eagerly fetch them).

Since I'm using a native query, join fetch with JPQL and entity load graphs are not an option.

My current approach is using a @SqlResultSetMapping to map the query result to the entities with @EntityResult (one for the parent and one for the child). I then have the Spring Data repository interface method returning a List<Tuple> and then have a default wrapper method that iterates through the list, "attaching" the child entities to the parent, and returning the parent entity.

Here is some example code.

Parent entity:

@Data
@Entity
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@SqlResultSetMapping(
        name = "parentMapping", 
        entities = {
            @EntityResult(entityClass = Parent.class), 
            @EntityResult(entityClass = Child.class)
        }
)
@NamedNativeQuery(
        name = "Parent.findByParentId0", 
        query = "SELECT * FROM parent p JOIN child c ON c.parent_id = p.parent_id", 
        resultSetMapping = "parentMapping")
public class Parent implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @Column(name = "PARENT_ID")
    @EqualsAndHashCode.Include
    private String id;
    
    @Setter(AccessLevel.NONE)
    @OneToMany(
            mappedBy = "parent", 
            cascade = CascadeType.ALL, 
            orphanRemoval = true)
    private Set<Child> children;
    
    public Set<Child> getChildren() {
        return unmodifiableSet(children);
    }
    
    public void addChild(final Child child) {
        requireNonNull(child);
        children.add(child);
        child.setParent(this);
    }
    
    public void removeChild(final Child child) {
        children.remove(requireNonNull(child));
        children.setParent(null);
    }
    
}

Child entity:

@Data
@Entity
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Child implements Serializable {

    private static final long serialVersionUID = 1L;
    
    @Id
    @Column(name = "CHILD_ID")
    private Long id;
    
    @ToString.Exclude
    @EqualsAndHashCode.Include
    @JoinColumn(name = "PARENT_ID", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Parent parent;
    
}

Spring Data JPA repository interface:

@Named
interface ParentRepository extends JpaRepository<Parent, String> {
    
    @SneakyThrows
    default Optional<Parent> findByParentId(final String parentId) {
        List<Tuple> results = findByParentId0(parentId);
        Parent parent = null;
        Set<Child> children = new HashSet<>();
        for (Tuple row : results) {
            parent = row.get(0, Parent.class);
            children.add(row.get(1, Child.class));
        }
        if (parent == null)
            return Optional.empty();
        Field field = Parent.class.getDeclaredField("children");
        field.setAccessible(true);
        field.set(parent, children);
        return Optional.of(parent);
    }
    
    @Query(nativeQuery = true)
    List<Tuple> findByParentId0(String parentId);
    
}

This works but there are some very big drawbacks. If I use the parent entity setter (or reflection on the field which I'm doing here) to attach the collection to the parent and orphanRemoval is set to true on the association (which it is and needs to be for other use cases), I will get the error 'a collection with cascade=”all-delete-orphan” was no longer referenced by the owning entity instance'.

If I add to the collection by accessing it directly (using either add or addAll), then JPA will submit another query to fetch the collection (since the FetchType is LAZY). This obviously defeats the purpose since I already have the child entities fetched from the native query.

Is there any way I can "attach" the child entities to the parent without the extra fetch and in a way so it is managed by JPA so orphanRemoval works?

0 Answers
Related