1

I have a service that runs async task at startup that runs recursively:

@Service
public class TaskService {

    private final TaskRepository taskRepository;

    @Inject
    public TaskService(TaskRepository taskRepository) {
        this.taskRepository= taskRepository;
    }

    private final int currentTaskId = -1;

    @Transactional
    @PostConstruct
    private void init() {
        taskRepository.findByClosedDateIsNull().forEach(taskRepository::delete);
        runTask();
    }

    @Async
    @Transactional
    private void runTask() {
        if (!getCurrent().isPresent()) {
            Task task = new Task();
            //set props
            currentTaskId = taskRepository.save(task).getId(); 
        }
        Util.sleep(5000); //wrapper for simple Thread.sleep(long l).
        Task task = getCurrent().get();
        if (task.getEvents().size > 0) {
            //bussiness logic
            Util.sleep(1000);
        }
        runTask();
    }

    @Transactional(readOnly = true)
    private Optional<Task> getCurrent() {
        return taskRepository.findOneById(currentTaskId).map(task -> {
            task.getEvents().size(); //throws the error here
            return task;
        });
    }

}

StackTrace:

Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.test.domain.Task.events, could not initialize proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:576) at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:215) at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:156) at org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:160) at com.test.service.TaskService.lambda$getCurrent$5(TaskService.java:135) at java.util.Optional.map(Optional.java:215) at com.test.service.TaskService.getCurrent(TaskService.java:134) at com.test.service.TaskService.runTask(TaskService.java:163) at com.test.service.TaskService.init(TaskService.java:66) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:365) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:310) at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:133) ... 21 common frames omitted

I also tried Hibernate.initialize(Object proxy);. OpenViewSessionFilter is not the solution for me. I don't want to set this collection EAGER.

5
  • Why is OpenViewSessionFilter not the solution? Can you use a fetch plan and make it eager for this query? Commented Oct 13, 2016 at 21:39
  • OpenViewSessionFilter decreases performance. + most of the methods like getCurrent() in other services works. I think the issue in the @Async, many @Transactional or recursive. I can't make it eager because I use JpaRepository. It cannot return eager collection if it's lazy. I'm trying to fetch collection in the getCurrent() method. Commented Oct 13, 2016 at 21:44
  • You're performing an explicit sleep in your handler. The overhead from the filter is not relevant. Commented Oct 13, 2016 at 22:40
  • Is it a way to sleep the method (maybe, Spring-way) to make it work? How can I deal without sleep? Commented Oct 13, 2016 at 22:50
  • First Spring uses proxies for things like transactions and async behavior. So basically your @ASync and @Transactional on the private method are useless (the method call doesn't pass through the proxy). Second there is no garantuee that the @Transactional on the init method is already applied (it might or might not be). Basically doing things like this in an @PostConstruct method isn't the right thing to do. Next to that you eventually will also run into a stack overflow from how you have setup your code right now. Calling runTask from runTask eventually breaks. Commented Oct 14, 2016 at 5:54

2 Answers 2

1

I found a solution (don't know if it's a bad practice, but it works for me). Just create a default method in your JpaRepository and annotate it with @Transactional. In the service call this method.

Sign up to request clarification or add additional context in comments.

1 Comment

Nice answer! The logic stays in the repository
0

For me it solved the problem when I removed the @Transactional on the first function call (in your case this would be init()) and moved the @Async functionality to a separate bean.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.