2

Lazy loading for one to many does not work

I have the following Order entity

@Data
@Entity
@EqualsAndHashCode
@Table(name = Order.TABLE_NAME)
public class Order implements Serializable {

private static final long serialVersionUID = -7036337819884484941L;

@Column(name = OrderNames.ORDER_ID)
private String orderId;

@Column
private String name;

@JsonManagedReference
@ToString.Exclude
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<OrderItem> orderItem = new HashSet<>();

and I have another entity OrderItem

@Data
@Entity
@EqualsAndHashCode(callSuper = true, exclude = "order")
@Table(name = OrderItemNames.TABLE_NAME)
public class OrderItem implements Serializable {

private static final long serialVersionUID = -7036337819884484941L;

@Column(name = OrderItemNames.ORDER_ID)
private String orderId;

@Column(name = OrderItemNames.ORDER_ITEM_ID)
private String orderItemId;

@JsonBackReference
@ToString.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = OrderItemNames.ORDER_FK, nullable = false)
private Order order;

Here is my repository

@Repository
public interface OrderRepository extends JpaRepository<Order, String> {
    Order findByOrderId(String orderId);
}

And here is my ServiceImpl

@Override
public Order findByOrderId(String orderId) {
    Order order = orderRepository.findByOrderId(orderId);

    return order;
}

From my understanding, if I debug on the orderRepository.findByOrderId, I expect it will only show Order entity (without OrderItem) because the fetch type is lazy

But the actual result is eager and the order has the orderItem entity as well regardless if I do it eagerly / lazily.

I have followed this as well https://stackoverflow.com/a/37727206/6460497 but to no avail. Do I miss something regarding the ToString or @EqualsAndHashCode ?

EDIT:

I turned on the SQL logging and it does 2 queries (select the order table and then select the orderItem table). This happens on both eager and lazy loading.

I also tried to remove lombok @Data and use @Getter @Setter create my own equal hashCode and toString, but it still load the data even if i set it to lazy.

Here is my properties to postgreSql

spring.jpa.database=postgresql
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.properties.hibernate.default_schema=public
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.open-in-view = false

hibernate.show_sql=true
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.format_sql=true
hibernate.hbm2ddl.auto=

spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=username
spring.datasource.password=password
Timothy
  • 107
  • 3
  • 11
  • is your `OneToMany` declaration ok in `Order ` class? shouldn't it be a `list/hashset` ? – user404 Jul 16 '19 at 10:12
  • 1
    Using Lombok is dangerous in a JPA environment as lazily using `@Data`, `@ToString` `@EqualsAndHashCode` etc without thinking about it can make very bad things happen. https://mdeinum.github.io/2019-02-13-Lombok-Data-Ojects-Arent-Entities/ – Alan Hay Jul 16 '19 at 10:15
  • @user404 my bad, it is Set, and it still does not work – Timothy Jul 16 '19 at 10:15
  • What do you mean by *the actual result is eager and the order has the orderItem entity as well*. Where do you see this? – Alan Hay Jul 16 '19 at 10:18
  • @AlanHay i debug it at ServiceImpl, if it is lazy, the orderItem should state that it cannot be loaded because it is lazy loading. as for the links you shared, i already put ToString.Exclude for the one to many field – Timothy Jul 16 '19 at 10:20
  • 1
    *the orderItem should state that it cannot be loaded because it is lazy loading*. If you have a transaction wrapping your service then it will be loaded when you access it by some means. If there is no transaction then it would trigger a lazy loading exception. You should turn on SQL logging to get a better idea of what is happening. – Alan Hay Jul 16 '19 at 10:23
  • @AlanHay in that case, wouldn't there be no possible lazy loading at all? I have an older service that does not use lombok and is spring project, it work as expected (when I debug it, the lazy entity is still empty). In my case i use lombok and spring boot. i turned on the logging and it select both tables (is it expected? or it should only select the non-lazy table?) – Timothy Jul 16 '19 at 10:28
  • *in that case, wouldn't there be no possible lazy loading at all?* I am not sure what this means. – Alan Hay Jul 16 '19 at 10:36
  • You also don't need the query method. Replace `Order order = orderRepository.findByOrderId(orderId);` with `Order order = orderRepository.findById(orderId);` which you inherit from JPA repository. – Alan Hay Jul 16 '19 at 10:37
  • @AlanHay for the query method, it is just as an example for simplicity. I log both eager and lazy load and they both show the same query (it created 2 queries, first it select the order table and then select orderItem table – Timothy Jul 16 '19 at 10:47

3 Answers3

2

There is a high chance Order is getting initialized when getOrderItems() is called while debugging if OpenEntityManagerInView interceptor is configured.

To turn this off in spring boot project you can use the following option in your application.properties file.

spring.jpa.open-in-view=false

You can check whether Order is getting fetched with a SQL JOIN query or with a different SELECT query by enabling <property name="show_sql">true</property> in your persistent.xml or by using the following if using spring boot.

spring.jpa.properties.hibernate.show_sql=true

You can see a sample for testing the scenario in this repository.

Rakib
  • 145
  • 13
  • i turned on the logging and it select both tables (is it expected? or should it select the non-lazy table?) – Timothy Jul 16 '19 at 10:31
  • Now can you use fetch = FetchType.EAGER and see if there's any difference in sql query? – Rakib Jul 16 '19 at 10:35
  • i tried and they both show the same query (it created 2 queries, first it select the order table and then select orderItem table) – Timothy Jul 16 '19 at 10:44
1

You can put on "order" the annotation

@Getter(AccessLevel.NONE)

segito10
  • 379
  • 6
  • 14
-1

I have found the answer by adding the following properties

spring.jpa.open-in-view = false

Also I found out that because I am debuging the entity, it seems that it does eager loading where actually it works as expected (lazy).

@Override
public Order findByOrderId(String orderId) {
    Order order = orderRepository.findByOrderId(orderId);
    return order;
}

say i put a debug point at

return order;

at this point, the db only do one query (for order) and then I lookup the entity like this then only hibernate do the query for the child entity.

Timothy
  • 107
  • 3
  • 11
  • 1
    You marked your answer as the right one, while someone else answered before you, and you didn't marked his but yours. – rpajaziti Sep 01 '20 at 11:55
  • @rpajaziti if you can read the entire conversation including the comments, while the first answer does provide a partial solution, i also found a mistake on my part, that i mentioned in my answer, so that people will be aware of that not because i am looking for "upvotes" but yeah, thanks for the feedback, and if you want to help the community, you don't need to be passive aggressive and judging people "Ironic, you should get a ban for that" - which is ironic – Timothy Sep 03 '20 at 06:08