Nick Div
Nick Div

Reputation: 5628

Spring boot JPA - Lazy loading is not working for One to One mapping

Please note that I have looked at similar questions and I have explained why they haven't worked for me

I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)

User Entity

@Entity
    @Table(name = "users")
    public class User implements Serializable {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Integer id;

        @Column
        private String name;

        @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
        private Address address;

        @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
        private Set<Note> notes;

    }

Address Entity

   @Entity
    @Table(name = "addresses")
    public class Address implements Serializable {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Integer id;

        @Column
        private String street;

        @Column
        private String city;

        @JsonIgnore
        @OneToOne
        @JoinColumn(name = "user_id")
        private User user;

    }

Note Entity

@Entity
    @Table(name = "notes")
    public class Note implements Serializable {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Integer id;

        @Column
        private String date;

        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "user_id", nullable = false)
        private User user;

    }

My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.

I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:

Post 1

I also read that spring.jpa.open-in-view defualt value might be the culprit:

Post 2

Post 3

So i tried the following options:

I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me

Could not write JSON: failed to lazily initialize a collection of role error

I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:

pom.xml

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>2.9.9</version>
</dependency>

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        for (HttpMessageConverter converter : converters) {
            if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
                ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
                mapper.registerModule(new Hibernate5Module());
            }
        }
    }

}

This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.

I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.

I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.

As mentioned by one of the users that it might a duplicate of another question on SO: Suggested Possible duplicate I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding

mapper.registerModule(new Hibernate5Module());

brings back the address associated to the User in the response.

Upvotes: 0

Views: 3033

Answers (3)

Itay wazana
Itay wazana

Reputation: 259

The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use @jsonIgnore so at other places you could return an Eager obj.

You can use the @jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :

Create view class:

public class ViewFetchType {
    static class lazy{ }
    static class Eager extends lazy{ }
}

Annotate your Entity

@Entity
public class User {

    @Id
    @JsonView(ViewFetchType.Lazy.class)
    private String id;

    @JsonView(ViewFetchType.Eager.class)
    @OneToOne( fetch = FetchType.LAZY)
    private Address address ;
}

Specify the FetchType class in your controller:

public class UserController {

    private final UserRepository userRepository;

    @Autowired
    UserController(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @RequestMapping("get-user-details")
    @JsonView(ViewFetchType.Eager.class)
    public @ResponseBody Optional<User> get(@PathVariable String email) {
        return userRepository.findByEmail(email);
    {

    @RequestMapping("get-all-users")
    @JsonView(ViewFetchType.Lazy.class)
    public @ResponseBody List<User> getUsers() {
        return userRepository.findAll();
    }
}

Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200

Upvotes: 1

Emanuel Ramirez
Emanuel Ramirez

Reputation: 412

You may take a look at Jackson Serialization Views.

I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.

By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.

Upvotes: 1

Ashish Soni
Ashish Soni

Reputation: 61

It's working as in the JPA spec:-

Refer the below URL https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html

LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.

Eager is mandatory (as the javadoc says the data must be eagerly fetched).

Upvotes: 1

Related Questions