Spring @Async: нулевая спящая сессия в коллекции LAZY

Я использую @Async аннотация на метод сервисного уровня.

Все отлично работает, когда я EAGERLY загружаю поля коллекции @OneToMany, но когда я пытаюсь получить доступ к загруженному элементу LAZY, я обнаружил, что Hibernate SessionImplementor объект session нулевой. Это, очевидно, дает мне исключение:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:
....    

Вот поле моей коллекции:

@OneToMany(mappedBy="abc", fetch=FetchType.LAZY, cascade=CascadeType.REMOVE)
@OrderBy(value="xsd asc")
@JsonIgnore
private Set<Item> items = new HashSet<Item>();

Как я могу связать Hibernate сессии, чтобы лениво загрузить мой объект внутри @Async контекст?

РЕДАКТИРОВАТЬ

Вот моя конфигурация trancactionManager / entityManager

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="emf"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager" />

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter">

    </property>
    <property name="packagesToScan" value="it.domain"/>

    <property name="persistenceUnitName" value="persistenceUnit"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
            <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
            <!--${hibernate.format_sql} -->
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
            <!-- ${hibernate.show_sql} -->
            <prop key="hibernate.show_sql">false</prop> 

            <prop key="hibernate.connection.charSet">UTF-8</prop>

            <prop key="hibernate.max_fetch_depth">3</prop>
            <prop key="hibernate.jdbc.fetch_size">50</prop>
            <prop key="hibernate.jdbc.batch_size">20</prop>

            <prop key="org.hibernate.envers.audit_table_suffix">_H</prop>
            <prop key="org.hibernate.envers.revision_field_name">AUDIT_REVISION</prop>
            <prop key="org.hibernate.envers.revision_type_field_name">ACTION_TYPE</prop>
            <prop key="org.hibernate.envers.audit_strategy">org.hibernate.envers.strategy.ValidityAuditStrategy</prop>
            <prop key="org.hibernate.envers.audit_strategy_validity_end_rev_field_name">AUDIT_REVISION_END</prop>
            <prop key="org.hibernate.envers.audit_strategy_validity_store_revend_timestamp">True</prop>
            <prop key="org.hibernate.envers.audit_strategy_validity_revend_timestamp_field_name">AUDIT_REVISION_END_TS</prop>               
        </props>
    </property>
</bean>

<jpa:repositories base-package="it.repository"
                  entity-manager-factory-ref="emf"
                  transaction-manager-ref="transactionManager"/>

<jpa:auditing auditor-aware-ref="auditorAwareBean" />
<bean id="auditorAwareBean" class="it.auditor.AuditorAwareBean"/>

3 ответа

Решение

Контекст транзакции Spring сохраняется с помощью ThreadLocals. Это означает, что ваша SessionFactory доступна только для потока, отправляющего ваш запрос, поэтому, если вы создадите новый поток, вы получите null и соответствующее исключение.

Что твое @Async Метод использует TaskExecutor для запуска вашего метода в другом потоке. Таким образом, проблема, описанная выше, происходит с вашим сервисом.

Я цитирую документы Spring JpaTransactionManager:

Реализация PlatformTransactionManager для одного JPA EntityManagerFactory. Связывает JPA EntityManager из указанной фабрики с потоком, потенциально позволяя один привязанный к потоку EntityManager на фабрику. SharedEntityManagerCreator и @PersistenceContext знают о связанных с потоками менеджерах сущностей и участвуют в таких транзакциях автоматически. Использование любого из них требуется для кода доступа JPA, поддерживающего этот механизм управления транзакциями.

Если вы хотите сохранить аннотацию, вам следует взглянуть на Hibernate CurrentSessionContext и как-то самостоятельно управлять сеансами.

Смотрите этот вопрос для получения дополнительной информации.

У меня была та же проблема, я потратил несколько дней, пытаясь найти решение, и наконец нашел решение. Я хотел бы поделиться подробностями, которые я нашел для тех, у кого может быть такая же проблема.

1-й - твой @Async-аннотированный метод должен быть объявлен в отдельном компоненте, а не в @Controller- или же @RestControllerаннотированная фасоль

2-й - Вам обязательно нужно объявить метод @Transactional который вызывается изнутри @Async заявленный метод. Тем не менее, самый первый метод вызывается из @Async метод должен быть определен @Transactional, У меня был @Transactional метод на втором или третьем уровне в стеке выполнения метода, поэтому проблема не была решена, и я потратил два дня, пытаясь выяснить, что происходит.

Так что лучше всего сделать

@Controller
ControllerBean {

    @Autowired
    AsyncService asyncService;

    public controllerMethod() {
        asyncService.asyncMethod();
    }
}

@Service
AsyncService {
    @Autowired
    TransactionalService transactionalService;

    @Async
    public asyncMethod() {
        transactionalService.transactionalMethod();
    }
}

@Service
TransactionalService {
    @Autowired
    SomeOtherService someOtherService;

    @Autowired
    EntityRepository entityRepository;

    @Transactional
    public transactionalMethod() {
        Entity entity = entityRepository.findOne(12345);

        someOtherService.doWork(entity);
    }
}

@Service
SomeOtherService {

    @Autowired
    EntityRepository entityRepository;

    @Transactional
    public doWork(Entity entity) {
        // fetch lazy properties, save, etc. without any session problems...
        entity.getLazyProperties(); 

        entityRepository.save(entity);
    }
}

В нормальных условиях (без @Async) транзакция распространяется через иерархию вызовов от одного компонента Spring к другому.

Когда @Transactional весна @Component вызывает метод с комментариями @Async этого не происходит Вызов асинхронного метода планируется и выполняется позднее исполнителем задачи и, таким образом, обрабатывается как "свежий" вызов, то есть без транзакционного контекста. Если @Async метод (или компонент, в котором он объявлен) не является @Transactional Spring сам по себе не будет управлять необходимыми транзакциями.

Попробуйте аннотировать метод, который вызывает @Async метод, и скажите нам, если работал.

Это зависит от того, как и где происходит сопоставление.

Если вы хотите использовать @Async вместе с LAZY loading, метод, объявленный с помощью @Transactional, должен реализовывать логику LAZY загрузки.

Если LAZY загрузка инициируется вне @Transactional, она не будет работать.

Другие вопросы по тегам