4

I have following two domain objects Suggestion and UserProfile

They are mapped with each other in one to many relationship. When I fetch all suggestions using Spring Data JPA, I get corresponding user object with each suggestion objects. This result is observed even when I have set fetch as FetchType.Lazy. Following is my code:

Suggestion.java

@Entity
@Table(name="suggestion")
@JsonIgnoreProperties({"suggestionLikes"})
public class Suggestion {

public Suggestion() {
    // TODO Auto-generated constructor stub
}

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

@Column(name="description")
private String description;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="suggestion_by")
private UserProfile user;

//getters and setters
}

UserProfile.java

@Entity
@Table(name = "user_master")
@JsonIgnoreProperties({"suggestions", "suggestionLikes"})
public class UserProfile implements Serializable {

/**
 * 
 */
private static final long serialVersionUID = 7400472171878370L;


public UserProfile() {

}


@Id
@NotNull
@Column(name = "username", length = 55)
private String userName;

@NotNull
@Column(name = "password")
private String password;

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

//getters and setters
}

Following is the service which fetches the records:

@Override
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<Suggestion> getAllSuggestion() {
    return suggestionRespository.findAll();;
}

SuggestionRepository:

@Repository
public interface SuggestionRespository extends JpaRepository<Suggestion, 
Integer> {

    public List<Suggestion> findAll();
}

Following is the Application class:

@EnableTransactionManagement
@SpringBootApplication
public class AngularSpringbootApplication {

public static void main(String[] args) {
    SpringApplication.run(AngularSpringbootApplication.class, args);
}
}

application.properties:

spring.datasource.url=jdbc:mysql://localhost:3306/plan_trip
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.properties.hibernate.dialect = 
org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto = update
spring.jackson.serialization.fail-on-empty-beans=false

Response received when getAllSuggestions() is executed:

[
{
    "suggestionId": 2,
    "description": "Germanyi!",
    "createdBy": "vinit2",
    "createdDate": "2018-06-19T10:38:32.000+0000",
    "modifiedBy": "vinit2",
    "modifiedDate": "2018-06-19T10:38:32.000+0000",
    "user": {
        "userName": "vinit2",
        "password": 
"$2a$10$.hP0sQWpl6qqDKiNTkiu0OciQeHRFnkEbEWcDvnv1HY4QCi2tKo.2",
        "firstName": "Vinit2",
        "lastName": "Divekar2",
        "emailAddress": "vinit@gmail.com",
        "createdBy": null,
        "modifedBy": null,
        "createdDate": "2018-06-04",
        "modifiedDate": "2018-06-04",
        "isActive": "1",
        "handler": {},
        "hibernateLazyInitializer": {}
    }
},
{
    "suggestionId": 1,
    "description": "Vasai!",
    "createdBy": "vinit1",
    "createdDate": "2018-06-19T10:37:38.000+0000",
    "modifiedBy": "vinit1",
    "modifiedDate": "2018-06-19T10:37:38.000+0000",
    "user": {
        "userName": "vinit1",
        "password": "$2a$10$D0RMSTWu03Jw7wC1/zqFxOOjb0Do24o/4mq2PhDhRUyBrs8bdGvUG",
        "firstName": "Vinit1",
        "lastName": "Divekar1",
        "emailAddress": "vinit@gmail.com",
        "createdBy": null,
        "modifedBy": null,
        "createdDate": "2018-06-04",
        "modifiedDate": "2018-06-04",
        "isActive": "1",
        "handler": {},
        "hibernateLazyInitializer": {}
    }
}

]

Expected response:

[{
    "suggestionId": 2,
    "description": "Germanyi!",
    "createdBy": "vinit2",
    "createdDate": "2018-06-19T10:38:32.000+0000",
    "modifiedBy": "vinit2",
    "modifiedDate": "2018-06-19T10:38:32.000+0000"
},
{
    "suggestionId": 1,
    "description": "Vasai!",
    "createdBy": "vinit1",
    "createdDate": "2018-06-19T10:37:38.000+0000",
    "modifiedBy": "vinit1",
    "modifiedDate": "2018-06-19T10:37:38.000+0000"
}
]

When I have declared FetchType as Lazy, I should not get User objects (in JSON) when I execute findAll()on Suggestion entity.

What am I missing here?

Vinit Divekar
  • 616
  • 7
  • 18
  • 6
    You're missing what lazy means. lazy doesn't mean "store null instead of the user in the loaded suggestions". It means "only load the actual user data from the database when the code tries to access this data for the first time", for example using `suggestion.getUser().getUsername()`. Also, don't cast the List to an ArrayList. There is absolutely no guarantee that the returned list is an ArrayList, and your code shouldn't care. Why are you doing that? – JB Nizet Jun 30 '18 at 07:27
  • @JBNizet, thanks for your response. What I understand from 'Lazy' fetch means is; 'Don't give me children until I ask them for when I access their parent'. Is that correct? I referred [this question](https://stackoverflow.com/q/2192242/8556538). Also, thank you for the suggestion, I will update my question accordingly. I have updated – Vinit Divekar Jun 30 '18 at 09:26
  • No. It means that the state of the child will only be loaded from the database when you try to access this state for the first time, i.e. call a method on the child object for the first time. – JB Nizet Jun 30 '18 at 09:28
  • Ohk, Sure. In the code given, I don't call the `getUser()` method for all suggestions. I just call `getAllSuggestion()`. – Vinit Divekar Jun 30 '18 at 09:33
  • 1
    Again, calling getUser() is not what will load the state of the user from the database. Calling a method **on the User returned by getUser()** will do that. Why do you think lazy loading is not working? How did you make this conclusion? – JB Nizet Jun 30 '18 at 09:34
  • I thought Lazy loading is not working because I shouldn't be getting User object (which is child of suggestion) when I query for suggestions. If lazy loading is not the way to stop (which I thought it to be) that from happening then what is the way? – Vinit Divekar Jun 30 '18 at 09:42
  • 1
    *I shouldn't be getting User object* what does that mean? Which code did you execute, what did you expect it to do, and what did it do instead? You will always have a User inside a suggestion, unless the suggestion is not linked to any user. But the **state** of the User will be loaded lazily. That's what matters: avoid useless SQL queries. If you never call any method on the user of the suggestion, no query will be executed to load the user data from the database. – JB Nizet Jun 30 '18 at 09:46
  • _I shouldn't be getting User object_ if this caused confusion, I meant the user object should not be received in the JSON response. I have updated question with the actual and expected response for the request which consumes `getAllSuggestions()`. – Vinit Divekar Jun 30 '18 at 10:00
  • Ah. So the problem has nothing to do with Hibernate or lazy loading. It has to do with the fact that you're asking Spring/Jackson to serialize a Suggestion to JSON, and that it serializes the User with the Suggestion (and thus causes the lazy loading to happen, since, when transforming the suggestion to JSON, it gets the user, and calls the getters on the user). The solution is to use Jackson annotations to ignore the user, or to transform your entities to DTOs containing only the data you want to return. – JB Nizet Jun 30 '18 at 10:03
  • Ohkk.. I can't ignore user from it because it will simply ignore when I do want the user for suggestion. Second options sounds to be the solution then? – Vinit Divekar Jun 30 '18 at 10:14
  • Yes. I never use entities as a response to my API. Entities are the persistence model. They shouldn't be the REST API model. The two should be decoupled. – JB Nizet Jun 30 '18 at 10:17
  • Spring Boot by default registers `OpenEntityManagerInViewInterceptor` to apply the Open EntityManager in View pattern, to allow for lazy loading in web views. If you do not want this behavior, you should set `spring.jpa.open-in-view` to `false` in your `application.properties` – Dirk Deyne Jun 30 '18 at 18:33
  • Have you managed to resolve this issue? – simonC Jan 10 '19 at 11:52

2 Answers2

6

You can use @JsonManagedReference & @JsonBackReference to prevent proxy call by jakson. Following code may help you.

@JsonBackReference
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Suggestion> suggestions;

@JsonManagedReference
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "suggestion_by")
private UserProfile user;

Add the following dependency, change version according to your hibernate

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

and finally add a new config

@Configuration
public class JacksonConfig {

@Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
            jacksonObjectMapperBuilder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            jacksonObjectMapperBuilder.modules(new Hibernate5Module());
        }

    };
 }
}
maruf571
  • 1,552
  • 1
  • 18
  • 18
4

when you declare fetch = FetchType.LAZY, it means hibernate create a proxy for this field in runtime. when getter of this field is called. the hibernate executes another select to fetch this object. in case of your problem getter of "user" field is called by Jackson (if you use rest). so if you don't want "user", try using a model mapper framework (dozer mapper is a good framework).