Search code examples
javaspring-boothibernate-criteria

Why is Hibernate with Specification making two queries when the first one got everything that was asked?


I'm making a practice Spring Boot application.

I've got 3 entities(edited as recommended):

class User extends EntityModelTemplate {
   String username; //unique

   @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
   List<UserRoles> userRoles; 
}

@IdClass(UserRolesKey.class)
class UserRoles {

   @Id
   @ManyToOne
   @JoinColumn(name = "user_id")
   User user;

   @Id
   @ManyToOne(fetch = FetchType.LAZY)
   @JoinColumn(name = "role_id")
   Role role;
}


class Role extends EntityModelTemplate{
   String role;

   @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "role")
   List<UserRoles> userRoles;
}

I wanted to make a single DB call which would fetch user along with roles so I tried using Specifications. I read the documentation and searched examples online before coming up with something that should work like I wanted it to.

Service method(edited as recommended):

public UserDTO getUserForSecurityCheck(String username) throws UsernameNotFoundException {
    log.info("Repository call start!");
    Optional<User> user = userRepo.findOne(Specification.where(UserSpecifications.usernameEquals(username)));
    log.info("Repository call end!");
    return user.map(UserDTO::new).orElseThrow(() -> new UsernameNotFoundException("Username "+username+" not found!"));
}

Specification:

public static Specification<User> usernameEquals(String username){

    return (root, query, criteriaBuilder) -> {
        ( (Join<Object, Object>) root.fetch(User_.USER_ROLES)).fetch(UserRoles_.ROLE);
        return criteriaBuilder.equal(root.get(User_.USERNAME), username);
    };
}

It works perfectly except Hibernate makes two DB calls.

(edit)

Logs

The first one selects all that is needed using two joins where username=? (it's rather long, hence the short, short version). Which is exactly what I wanted it to do. But then it makes a second call which literally does Select *(except userRoles) from user where user_id=?.

Why?

Even though I'm still a novice at coding in general, I was pretty sure I understood how Specification and Hibernate work. Basics, at least. Obviously I don't.

So, my question is: is it suppose to make two DB calls or what am I doing wrong?

Thanks in advance.


Solution

  • I've found my mistake and I'm pretty much embarrassed for not noticing it right away: User in UserRoles is loaded eagerly by default. That was the second call. I changed it to lazy and it's working as intended.

    If it weren't for M.Deinum asking about a lazy collection, I've wouldn't have noticed it in a million years.