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: