Home > Mobile >  Lazy fetching in Spring JPA - different behaviour within cron and REST request
Lazy fetching in Spring JPA - different behaviour within cron and REST request

Time:05-02

I have a little problem to understand why lazy fetching have different behaviour when I'm trying to execute 'findAll' method from REST controller and CRON job.

I have two simple entities:

@Entity
@Data
public class MainEntity {

    @Id
    @GeneratedValue(generator = "MainEntitySeqGen", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "MainEntitySeqGen", sequenceName = "main_entity_seq")
    private Long id;

    @OneToMany(fetch = FetchType.LAZY)
    @JoinColumn(name = "main_id", foreignKey = @ForeignKey(name = "FK_mainEntity_nestedEntity"))
    private Set<NestedEntity> nestedEntities;
}

and

@Entity
@Data
public class NestedEntity {

    @Id
    @GeneratedValue(generator = "NestedEntitySeqGen", strategy = GenerationType.SEQUENCE)
    @SequenceGenerator(name = "NestedEntitySeqGen", sequenceName = "nested_entity_seq")
    private Long id;

    @Column
    private String sample;
}

and service with method to find all 'MainEntities'. This service doesn't have any @Transactional annotations! (I don't have it in whole project)

    @Override
    public List<MainEntity> findAll() {
        log.info("Finding all main entities...");
        return repository.findAll();
    }

I have one additional service to test lazy initialization exception with method:

    @Override
    public void testMe() {
        List<MainEntity> entities = mainEntityService.findAll();
        if (!entities.isEmpty()) {
            log.info("Nested entities size: {}", entities.get(0).getNestedEntities().size());
        }
    }

Still no transactions on method or service!

Ok, and now, when I'm trying to execute testMe method from REST controller:

    @GetMapping
    public ResponseEntity<Void> testMe() {
        log.info("Execution from API: Not throwing LazyInitializationException");
        testService.testMe();
        return ResponseEntity.ok().build();
    }

I have correct output:

2022-05-01 00:04:42.396  INFO 11428 --- [nio-8080-exec-2] c.r.jpatest.web.FirstTestServiceApi      : Execution from API: Not throwing LazyInitializationException
2022-05-01 00:04:42.396  INFO 11428 --- [nio-8080-exec-2] c.r.j.s.impl.MainEntityServiceImpl       : Finding all main entities...
2022-05-01 00:04:42.396 TRACE 11428 --- [nio-8080-exec-2] o.s.t.i.TransactionInterceptor           : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
Hibernate: select mainentity0_.id as id1_0_ from main_entity mainentity0_
2022-05-01 00:04:42.397 TRACE 11428 --- [nio-8080-exec-2] o.s.t.i.TransactionInterceptor           : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
Hibernate: select nestedenti0_.main_id as main_id3_1_0_, nestedenti0_.id as id1_1_0_, nestedenti0_.id as id1_1_1_, nestedenti0_.sample as sample2_1_1_ from nested_entity nestedenti0_ where nestedenti0_.main_id=?
2022-05-01 00:04:42.408  INFO 11428 --- [nio-8080-exec-2] c.r.j.service.impl.TestServiceImpl       : Nested entities size: 5

Please note that TransactionInterceptor says, he completed transaction for 'findAll' (from repository) but nevertheless he's invoking SELECT query to fetch NestedEntities that should be loaded lazily and I expected it throw exception (but not).

And this same 'testMe' method executed from CRON:

@Scheduled(cron = "*/10 * * * * *")
    public void testCron() {
        log.info("Execution from CRON: Throwing LazyInitializationException");
        testService.testMe();
    }

is throwing exception:

2022-05-01 00:04:50.014  INFO 11428 --- [   scheduling-1] c.r.jpatest.cronservices.TestCron        : Execution from CRON: Throwing LazyInitializationException
2022-05-01 00:04:50.014  INFO 11428 --- [   scheduling-1] c.r.j.s.impl.MainEntityServiceImpl       : Finding all main entities...
2022-05-01 00:04:50.014 TRACE 11428 --- [   scheduling-1] o.s.t.i.TransactionInterceptor           : Getting transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
Hibernate: select mainentity0_.id as id1_0_ from main_entity mainentity0_
2022-05-01 00:04:50.015 TRACE 11428 --- [   scheduling-1] o.s.t.i.TransactionInterceptor           : Completing transaction for [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]
2022-05-01 00:04:50.016 ERROR 11428 --- [   scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler    : Unexpected error occurred in scheduled task

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.axl.jpatest.domain.MainEntity.nestedEntities, could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:614) ~[hibernate-core-5.6.8.Final.jar:5.6.8.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218) ~[hibernate-core-5.6.8.Final.jar:5.6.8.Final]
    at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162) ~[hibernate-core-5.6.8.Final.jar:5.6.8.Final]
    at org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:168) ~[hibernate-core-5.6.8.Final.jar:5.6.8.Final]
    at com.axl.jpatest.service.impl.TestServiceImpl.testMe(TestServiceImpl.java:23) ~[classes/:na]
    at com.axl.jpatest.cronservices.TestCron.testCron(TestCron.java:19) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) ~[spring-context-5.3.19.jar:5.3.19]
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) ~[spring-context-5.3.19.jar:5.3.19]
    at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:95) ~[spring-context-5.3.19.jar:5.3.19]
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) ~[na:na]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
    at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]

Does anyone have idea why it works like that?

I use: Spring Boot: v2.6.7 Hibernate: v5.6.8

CodePudding user response:

The error tell you that you're trying to fetch a lazy association, but there is no existing Hibernate Session for your current thread since you do it outside a transactional context.

Using JPA without delimiting your transaction boundaries explicitly with @Transactional is not a good thing because it creates an obscure code that creates a lot of short-lived Session and bypasses a lot of features that Hibernate offers you (session-level repeatable read, write-behind...)

CodePudding user response:

I founded answer for this "problem" - cause is "Open Session In View" Spring mechanism. Links:

  • Related