Search code examples
javahibernatemany-to-many

Hibernate: How do I fetch specific columns of entities in a Set within an entity?


I have a User entity with a relatedUsers (Set<User>) property within, and when fetching a User I don't want to load all properties of the entities within relatedUsers, just their id. I've been trying to accomplish this using an entity graph but with no success. This is my entity:

@Entity
@Table(name = "USERS")
@NamedEntityGraph(
        name = "User.users",
        attributeNodes = {
                @NamedAttributeNode(value = "users", subgraph = "usersSubgraph")
        },
        subgraphs = {
                @NamedSubgraph(name = "usersSubgraph", attributeNodes = {
                        @NamedAttributeNode("id")
                })
        }
)
public class User {
    public User() {}

    @Id
    @Column(name = "id", unique = true)
    Long id;
    
    @Column(name = "xpto")
    String xpto;
    
    @Column(name = "xpto2")
    String xpto2;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "USERS_USERS",
            joinColumns = @JoinColumn(name = "User_id"),
            inverseJoinColumns = @JoinColumn(name = "users_id"))
    Set<User> users;

    // ... the rest are the getters and setters
}

And this is the query:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);

EntityGraph<?> eg = entityManager.getEntityGraph("User.users");

Root<User> root = cq.from(User.class);

Predicate predicate = cb.or(cb.equal(root.get("id"), 1L));

cq.select(root).distinct(true).where(predicate);

TypedQuery<User> tq = entityManager.createQuery(cq);

return tq.setHint("jakarta.persistence.fetchgraph", eg).getSingleResult();

This query isn't executing as I expected (assuming i understood what the entity graph is capable of), as it seems the entity graph did nothing, since the entities of relatedUsers are still being fully loaded instead of just loading the ids. Is what i'm trying to do even possible? If so how can i accomplish it?


Solution

  • In Hibernate, the whole entity object is always fetched, it's not possible to only fetch a subset of the properties of an entity and still get an entity of that type. However, if you want only some particular fields, you can create a DTO projection in which you can encapsulate the data you want to return.

    public class UserIdProjection {
        private Long id;
    
        public UserIdProjection(Long id) {
            this.id = id;
        }
    
        // getter
    }
    

    And modify your query:

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<UserIdProjection> cq = cb.createQuery(UserIdProjection.class);
    
    Root<User> root = cq.from(User.class);
    
    Predicate predicate = cb.or(cb.equal(root.get("id"), 1L));
    
    cq.select(cb.construct(UserIdProjection.class, root.get("id"))).distinct(true).where(predicate);
    
    TypedQuery<UserIdProjection> tq = entityManager.createQuery(cq);
    
    List<UserIdProjection> results = tq.getResultList();
    

    This should return a list of UserIdProjection objects, with their related user id's. The original entity won't be fetched this way.