Search code examples
javahibernatespring-securitymany-to-many

Hibernate does not fetch ManyToMany relation data


I am trying to read data from the database, using Hibernate. I have following data model, which holds some user credentials data, split into several entities, with Entity C holding User authorization roles (user can have more than 1 role, hence ManyToMany):

A -> OneToOne -> B <- ManyToMany -> C

as written:

@Entity
public class A {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String s1;
    private String s2;
//getters, setters, etc.
}
@Entity
public class B {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String s1;
    private String s2;
    @OneToOne(cascade = CascadeType.REMOVE,
            fetch = FetchType.LAZY)
    private A aElement;
    @ManyToMany(targetEntity = C.class,
                fetch = FetchType.EAGER,
                cascade = CascadeType.ALL)
    @JoinTable(
            name = "b_x_c",
            joinColumns = @JoinColumn(table = "b", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(table = "c", referencedColumnName = "id")
    )
    private Set<C> cSet;
//getters, setters, etc.
}
@Entity
public class C {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    @ManyToMany(targetEntity = B.class,
            mappedBy = "cSet",
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER)
    private Set<B> bSet;
//getters, setters, etc.
}

I did write a CustomUserDetailService (using Spring Security for authorizaiton) for some user tweaking:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final LoginService loginService ;

    public CustomUserDetailsService(LoginService loginService ) {
        this.loginService = loginService ;
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return loginService.findCredentialsByS1(s)
                .map(this::createUserDetails)
                .orElseThrow(() -> new UsernameNotFoundException("no user found"));
    }

    private UserDetails createUserDetails(B credentials) {
        return User.builder()
                .username(credentials.getS1())
                .password(credentials.getS2())
                .roles(credentials.getCSet().toArray(String[]::new))
                .build();
    }
}

loginService is:

@Service
public class LoginService {
    public final BRepository bRepository;

    public ClientLoginService(BRepository bRepository) {
        this.bRepository = bRepository;
    }

    public Optional<B> findCredentialsByS1(String s) {
        return bRepository.findByS1IgnoreCase(s);
    }
}

and BRepository:

public interface BRepository extends JpaRepository<B, Long> {
        Optional<B> findByS1IgnoreCase(String s);
}

Now, when I login a User, CustomUserDetailsService is being invoked and loadUserByUsername() called, through which a Repository executes a call to the database, but somehow, the contents of C table are not loaded (they are properly connected in the intermediate (connector) database). Data of A table are being loaded, but C table not - and no error is given. What can be a reason here? I don't quite understand why, especially that when I turn on Hibernate logging, and look into the queries that are being given, it should actually provide correct data, but debugging loadUserByUsername shows, that it's empty.


Solution

  • SOLUTION

    I stumbled upon a topic which I didn't find previously, when creating this topic:

    ManyToMany relationship not working when executing query (Spring Data JPA)

    So in my case the problem was caused by Lombok's @Data annottation, which does not work well with JPA Entities, it seems. In my case, when I substituted @Data with autogenerated (in IntelliJ IDEA) getters, setters, hashCode() and equals(), it still didn't fix the problem, highlighting the source - tweaking with hashes may break (hash)Sets and in result - break the JPA mapping of objects. In this case, changing from Set to List did the trick. But after removing both autogenerated methods, Set works just fine with default implementations. So, that's one good lesson about hashes and their connection with Sets...

    P.S. I don't recommend changing types from Set to List in ManyToMany relations