Search code examples
javaspringspring-bootspring-data-jpaspring-data

Fetch type is set to lazy, but it still hibernate sends the second request


Although fetch type is lazy, hibernate for some reason sends the second request and fetches the lazy one. I have been dealing with this problem for a few days for some reason I don't understand. I tried some ways on the internet, but unfortunately I couldn't fix it, I would be very grateful if you could help me by looking at my code.

User Entity

@Entity
@Table(name = "users")
@Data
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    @Column(name = "username", length = 50)
    private String username;
    @Column(name = "email")
    private String email;
    @Column(name = "password")
    private String password;
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "user_friends" , joinColumns = @JoinColumn(name = "user_id") , inverseJoinColumns = @JoinColumn(name = "friend_id"))
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Set<User> friends = new HashSet<>();

}

User Service

@Service
@Transactional
public class UserService implements ICrudService<User> {
    @Autowired
    private UserRepository repository;

    @Override
    @Transactional
    public List<User> findAll() {
        return repository.findAll();
    }

    @Override
    @Transactional
    public User findById(Long id) throws Exception {
        return repository.findById(id).orElseThrow(() -> new Exception("Kullanici Bulunamadi"));
    }

    @Override
    @Transactional
    public User create(User user) {
        return repository.save(user);
    }

    @Override
    public void delete(Long id) {
        repository.deleteById(id);
    }
}

User Controller

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/all")
    public List<User> getUsers(){
        return userService.findAll();
    }

    @GetMapping("/{userId}")
    public User getUserById(@PathVariable Long userId) throws Exception {
        return userService.findById(userId);
    }

    @PostMapping
    public User saveUser(@RequestBody User user){
        return userService.create(user);
    }

    @DeleteMapping("/{userId}")
    public void deleteUserById(@PathVariable Long userId){
        userService.delete(userId);
    }
}

I use JpaReository as repository in service

And the output of "/users/all" looks like this

Output

[
    {
        "id": 1,
        "username": "testUser",
        "email": "[email protected]",
        "password": "verystrongpasword",
        "friends": []
    }
]

This is the hibernate sql format

Hibernate: 
    select
        u1_0.id,
        u1_0.email,
        u1_0.password,
        u1_0.username 
    from
        users u1_0
Hibernate: 
    select
        f1_0.user_id,
        f1_1.id,
        f1_1.email,
        f1_1.password,
        f1_1.username 
    from
        user_friends f1_0 
    join
        users f1_1 
            on f1_1.id=f1_0.friend_id 
    where
        f1_0.user_id=?

Solution

  • When fetch type for property is Lazy, the data is retrieved on demand - when the getter for the property is invoked. User is serialized as json when the response is sent, and json libraries normaly to this using the getters - getFriends() is invoked reflectively by the underlying library.

    The solution (and good practice) is to use a DTO for response/request, not entities.