Home > Software engineering >  How to filter one-to-many mapping based on entity fields in JPA?
How to filter one-to-many mapping based on entity fields in JPA?

Time:04-16

I can represent this query:

SELECT * FROM group g JOIN user u ON user.group_id = group.id

via the following in JPA:

@EntityGraph(attributePaths = {"users.posts.comments"})
Optional<Group> findEagerlyFetchedById(UUID id);

But how do I filter out some users based on a field? As in, how do I represent the following SQL query in JPA?

SELECT * FROM group g JOIN user u ON user.group_id = group.id WHERE user.isAuthenticated = true

I currently have the query below but it takes an all-or-nothing approach. If a single user has matching isAuthenticated field then it returns the group along with all users regardless of whether that field is true for that user. Also, if no users are authenticated, then the group isn't returned at all.

@EntityGraph(attributePaths = {"users.posts.comments"})
@Query("SELECT g FROM Group g JOIN g.users gu WHERE gu.isAuthenticated = :isAuthenticated AND g.id = :groupId")
Optional<Group> findEagerlyFetchedByUserAuthed(UUID groupId, boolean isAuthenticated);

For reference these are the entity definitions:

Group:

@Entity
public class Group {
    private UUID id;

    @OneToMany(
            mappedBy = "groups",
            fetch = FetchType.LAZY,
            cascade = CascadeType.ALL,
            orphanRemoval = true
    )
    private Set<User> users = Sets.newHashSet();
}

User:

@Entity
public class User {
    private UUID id;
    private Boolean isAuthenticated;

    @ManyToOne( fetch = FetchType.LAZY )
    private Group group;
}

CodePudding user response:

From what i am understanding is that you want to select all groups that have authenticated users.

  1. As I understand your problem with "then it returns the group along with all users regardless of whether" is that the database loads all the users, into the java/context even if this is not needed. The Problem for this is probably that even though the users are fetched lazy

    @OneToMany(
        mappedBy = "groups",
        fetch = FetchType.LAZY,
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private Set<User> users = Sets.newHashSet();
    

    java has to evaluate weather or not or not the entry in the set is unique. Depending on your implementation of equals for Group or User (not shown in your example) it might be possible that the value of all fields is called, therefore requiring the Set to be fully loaded. A solution for this could be replacing the Set with a List.

    private Set<User> users = new ArrayList<>();
    

    Depending on your toString() implementation of the classes it could also just be a problem with debugging since most debuggers call the toString() implementation when trying to display an Object inside the debugger.

  2. The second problem I understand you are approaching is "Also, if no users are authenticated, then the group isn't returned at all." I dont know how to help with that since your SQL clearly states

    " ....g.users gu WHERE gu.isAuthenticated = :isAuthenticated ..."
    

    this will always just return groups with authenticated users. Here i cant understand what your problem is. That is what i thought was your goal.

A practical approach that might help you could be selecting the Users and then accessing the groups (in Java via streams).

@Query("SELECT u FROM Users u  WHERE u.isAuthenticated = :isAuthenticated)
List<Users> findEagerlyFetchedByUserAuthed(boolean isAuthenticated);

or trying to do a sub select of users first and then joining with something like this:

@Query(
"SELECT group 
FROM from group 
where groupid IN (SELECT u.groupId 
            FROM Users u 
            WHERE u.isAuthenticated = :isAuthenticated))
Optional<Group> findEagerlyFetchedByUserAuthed(UUID groupId, boolean isAuthenticated);

My syntax here is probably not 100% correct but i hope you got the idea.

Lastly it might be better to use

List<Group> findEagerlyFetc...

instead of

Optional<Group> findEagerlyFetc.... 

CodePudding user response:

@EntityGraph with @Query not working properly. Use JPA method naming query with @EntityGraph

@EntityGraph(attributePaths = {"users.posts.comments"})
Optional<Group> findByIdAndUsers_IsAuthenticated(UUID groupId, boolean isAuthenticated);

Note: To resolve ambiguity we can use _ inside your method name to manually define traversal points.

  • Related