Search code examples
javaspring-boothibernatespring-data-jpalazy-loading

Fetch lazy initialized list from database via Spring JPA inside ThreadPoolTaskScheduler scheduled task


i got some problems while fetching some lazy data from database with JPA and Spring.

I got to schedule some database operations for the future, but when they were actually running, I got a LazyInitializationException.

This is the Message:

failed to lazily initialize a collection of role: de.db.ewb.database.entity.VorgangDO.zuege: could not initialize proxy - no Session

According to some information on Stackoverflow and the documentation, my transaction got closed when leaving the scope of the thread that originally acquired the VorgangDO object from the database, so the object is not bound to JPA and Hibernate anymore. That means no lazy loading anymore.

My first idea was to just call the .findById() method of my repo again but inside the new scope. So I will get a new object that is bound again to JPA. But the resulting object had all fields set to null, so I added the @Transaction annotation to my run() method.

The result was that the fields are now filled, but when I try to access a collection of data that should be lazy loaded, I still get the mentioned exception.

Even when I'm trying to fetch those data inside the same method, which is annotated with @Transactional with this line vorgangRepository.findById(55).get().getZuege().size().

This is my code:

@Service
public class CSPService {

    @Autowired
    private VorgangRepository vorgangRepository;

    private ThreadPoolTaskScheduler threadPoolExecuter;

    private class CSPRetry implements Runnable {
        ...

        @Override
        @Transactional
        public void run() {
            ...
            //The exceptioon is thrown here!
            vorgangRepository.findById(55).get().getZuege().size();
            ...
        }

        ...

    }

    @PostConstruct
    private void init() {
        threadPoolExecuter = new ThreadPoolTaskScheduler();
        threadPoolExecuter.setPoolSize(10);
        threadPoolExecuter.setThreadNamePrefix("CSPRetryTaskScheduler");
        threadPoolExecuter.setDaemon(true);
        threadPoolExecuter.initialize();
    }

    public void sendToCSP(VorgangDO vorgang) {
        ...
    }


    public void retry() {
        ...
        threadPoolExecuter.schedule(new CSPRetry(....), LocalDateTime.now().plusSeconds(30).toInstant(OffsetDateTime.now().getOffset()));
        ...
    }
}



@Entity
@Indexed
@Table(name = "VORGAENGE")
@SequenceGenerator(name = "hibernate_sequence_abstract_vorgang", sequenceName = "hibernate_sequence_vorgang", allocationSize = 1)
public class VorgangDO extends AbstractVorgangDO implements EntityWithRegions {
    ...
}

@MappedSuperclass
public class AbstractVorgangDO extends BaseDO<VorgangTO> {
    ...
    @IndexedEmbedded
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name = "vorgang_id")
    @IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW)
    private List<ZugDO> zuege;
    ...
}

public interface VorgangRepository extends CrudRepository<VorgangDO, Long> {
    ...
}

Does someone know why this still fails? Even when I'm initializing the collection inside the same transaction where i called .findById() before?

I boiled the code down to the minimum because it is a lot. If something is missing, I will provide the missing part.


Solution

  • You need to update fetch=FetchType.EAGER inside your @OneToMany annotation to automatically pull back the @Embeddable class:

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    

    Access to a lazy-loaded object outside of the context of an open Hibernate session will result in LazyInitializationException. Hibernate creates a dynamic Proxy Object subclass that will hit the database only when we first use the object. This error occurs when we try to fetch a lazy-loaded object (Lazy Loading means that the object won’t be loaded to the Session context until it is accessed in code) from the database by using a proxy object, but the Hibernate session (Session is the persistence context that represents a conversation between an application and the database) is already closed.