18

I googled a lot and It is really bizarre that Spring Boot (latest version) may not have the lazy loading is not working. Below are pieces of my code:

My resource:

 public ResponseEntity<Page<AirWaybill>> searchAirWaybill(CriteraDto criteriaDto, @PageableDefault(size = 10) Pageable pageable{
airWaybillService.searchAirWaybill(criteriaDto, pageable);
        return ResponseEntity.ok().body(result);
}

My service:

@Service
@Transactional
public class AirWaybillService {

//Methods

 public Page<AirWaybill> searchAirWaybill(AirWaybillCriteriaDto searchCriteria, Pageable pageable){
    //Construct the specification
            return airWaybillRepository.findAll(spec, pageable);
   }
}

My Entity:

@Entity
@Table(name = "TRACKING_AIR_WAYBILL")
@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@airWaybillId") //to fix Infinite recursion with LoadedAirWaybill class
public class AirWaybill{
//Some attributes
    @NotNull
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "FK_TRACKING_CORPORATE_BRANCH_ID")
    private CorporateBranch corporateBranch;
}

And when debugging, I still getting all lazy loaded attributed loaded. See image below.

enter image description here

One of my questions is could Jackson be involved in such behaviour? Is there any way that I may have missed to activate the lazy loading?

EDIT

Another question, could the debugger be involved in ruining the lazy loading?

EDIT 2:

For specification build, I have :

public static Specification<AirWaybill> isBranchAirWayBill(long id){
    return new Specification<AirWaybill>() {
        @Override
        public Predicate toPredicate(Root<AirWaybill> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
            return cb.equal(root.join("corporateBranch",JoinType.LEFT).get("id"),id);
        }
    };
}
Houssam Badri
  • 2,441
  • 3
  • 29
  • 60

11 Answers11

18

Hibernate Session exists within method with @Transactional. Passing entity outside Service class is a bad practise because session is being closed after leaving your search method. On the other hand your entity contains lazy initialised collections, which cannot be pulled once session is closed.

The good practise is to map entity onto transport object and return those transport objects from service (not raw entities).

Tomasz Białecki
  • 1,041
  • 6
  • 10
  • 6
    precise concise and understandable – Houssam Badri Apr 17 '19 at 06:20
  • 1
    I am sorry, but I don't understand it. Do you have an example somewhere showing how this transport design is setup? Thank you in advance – Alex Sep 20 '19 at 13:17
  • 1
    Please create simple POJO (let's say `AirWaybillTO` and put all attributes you need there, then map them from `AirWaybill` and return `AirWaybillTO` from your service. – Tomasz Białecki Aug 25 '20 at 07:44
8

SpringBoot by default has enabled:
spring.jpa.open-in-view = true
That means transaction is always open. Try to disable it.
more information here

Martin Čuka
  • 16,134
  • 4
  • 23
  • 49
  • Nice help, I'm now getting `org.hibernate.LazyInitializationException: could not initialize proxy - no Session`. I tried installing `Hibernate5Module` but did not work yet, still same error – Houssam Badri Apr 16 '19 at 09:54
  • You can fix this with @Transactional. See Kowalski's answer. – gagarwa Jun 25 '20 at 02:36
6

Most likely you are debugging while still being inside the service, thus while the transaction is still active and lazy loading can be triggered (any method called on a lazy element triggered the fetch from the database).

The problem is that lazy loading cannot occur while being outside of the transaction. And Jackson is parsing your entity definitely outside the boundaries of one.

You either should fetch all the required dependencies when building your specification or try with the @Transactional on the resource level (but try that as of last resort).

Just so that you know, LAZY fetching strategy is only a hint.. not a mandatory action. Eager is mandatory:

The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified.

Maciej Kowalski
  • 25,605
  • 12
  • 54
  • 63
3

When using a debugger, you are trying to access the value of your variables. So, at the moment you click that little arrow on your screen, the value of the variable in question is (lazily) loaded.

Sofo Gial
  • 697
  • 1
  • 9
  • 20
3

I suppose you are using Hibernate as JPA.

From specification:

The EAGER strategy is a requirement on the persistence provider runtime that data must be eagerly fetched. The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified. https://docs.jboss.org/hibernate/jpa/2.2/api/javax/persistence/FetchType.html

Hibernate ignores fetch type specially in OneToOne and ManyToOne relationships from non owning side.

There are few options how to force Hibernate use fetch type LAZY if you really need it.

  1. The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.
  2. The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add @LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.
  3. The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown JEE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case @LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.

For more informations look at this: http://justonjava.blogspot.com/2010/09/lazy-one-to-one-and-one-to-many.html

Delirante
  • 809
  • 8
  • 12
  • Same thing is mentioned in this blog written by Vlad Mihalcea about `@OneToOne` mapping and LAZY loading: https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ – maximus Aug 22 '22 at 14:07
1

Just a guess: you are forcing a fetch while building your specification.

I expect something like

static Specification<AirWaybill> buildSpec() {
    return (root, query, criteriaBuilder) -> {
       Join<AirWaybill, CorporateBranch> br = (Join) root.fetch("corporateBranch");
       return criteriaBuilder.equal(br.get("addressType"), 1);
    };
}

If this is the case, try changing root.fetch to root.join

Lesiak
  • 22,088
  • 2
  • 41
  • 65
1

Another consideration is while using Lombok, @Data/@Getter annotation causes to load lazy items without need. So be careful when using Lombok.

This was my case.

BEJGAM SHIVA PRASAD
  • 2,181
  • 1
  • 18
  • 27
0

The retrieved data already lazy but you are using debug mode its return value when click to watch a data from a debugger.

0

You can solve this problem with wit 2 steps with jackson-datatype-hibernate:

kotlin example

  1. Add In build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. Create @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

Notice that Module is com.fasterxml.jackson.databind.Module, not java.util.Module

Dmitry Kaltovich
  • 2,046
  • 19
  • 21
0

I think I might have a solution. You can give this a try. This worked for me after 4 hours of hit and trial -

User Entity :

class User {
    @Id
    String id;

    @JsonManagedReference
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Address> addressDetailVOList = new ArrayList<Address>();
} 

Address entity :

class Address {

    @JsonBackReference
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "userId")
    private User user;
}

Your parent class will use @JsonManagedReference, and child class will use @JsonBackReference. With this, you can avoid the infinite loop of entity objects as response and stack overflow error.

Apostolos
  • 10,033
  • 5
  • 24
  • 39
Vivek Singh
  • 93
  • 1
  • 5
-1

I also faced the same issue with Spring data JPA. I added the below annotation & able to get the customer records for a given ORDER ID

Customer to Order : one to Many

Order to customer is lazy load.

Order.java

@ManyToOne(cascade = CascadeType.ALL,targetEntity = CustomerEntity.class,fetch = FetchType.LAZY)
@Fetch(FetchMode. JOIN)
@JoinColumn(name = "CUSTOMER_ID",referencedColumnName = "CUSTOMER_ID",insertable = false,updatable = false)
@LazyToOne(LazyToOneOption.PROXY)
Private CustomerEntity customer

Customer.java

@Entity
@TabLe(name = "CUSTOMER" ,
uniqueConstraints = @UniqueConstraint(columnNames= {"mobile"}))
public class CustomerEntity {

@GeneratedVaLue(strategy = GenerationType.IDENTITY)
@CoLumn(name = "customer_id" )
private Integer customerld;
private String name;
private String address;
private String city;
private String state;
private Integer zipCode;
private Integer mobileNumber;

@OneToMany(mappedBy = " customer" )
@Fetch(FetchMode.JOIN)
@LazyToOne(LazyToOneOption.PROXY)
private List<OrderEntity> orders;
}
Gaurav
  • 3,615
  • 2
  • 27
  • 50
  • 3
    Please add code and data as text ([using code formatting](//stackoverflow.com/editing-help#code)), not images. Images: A) don't allow us to copy-&-paste the code/errors/data for testing; B) don't permit searching based on the code/error/data contents; and [many more reasons](//meta.stackoverflow.com/a/285557). Images should only be used, in addition to text in code format, if having the image adds something significant that is not conveyed by just the text code/error/data. – Suraj Rao Jan 07 '21 at 11:01
  • can someone explain this annotation. in Documentation, it is not properly explained. https://www.baeldung.com/hibernate-lazy-loading-workaround after reading this article, it seems @ Transactional is better, but if we add "enable_lazy_load_no_trans" then it will call for list of object & each object each refered lazy object in one trasnaction per object. Is @LazyToOne also will do one time one transaction or is it one extra txn in total only? – Satish Patro Jan 14 '21 at 05:51