Spring @Async: нулевой сеанс гибернации для коллекции LAZY

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

Все работает нормально, когда я ОБЯЗАТЕЛЬНО загружаю поля коллекции @OneToMany, но когда я пытаюсь получить доступ к лениво загруженному элементу, я обнаружил, что Hibernate SessionImplementor object session имеет значение null. Это, очевидно, дает мне исключение:

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>();

Как я могу привязать сеанс гибернации, чтобы ЛЕНЬНО загружать мой объект внутри контекста @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"/>

person gipinani    schedule 01.08.2014    source источник
comment
Используете ли вы управляемые Spring SessionFactory и TransactionManager?. Можете показать код метода?   -  person ElderMael    schedule 01.08.2014
comment
Метод загружает объект или просто обращается к нему?   -  person Sotirios Delimanolis    schedule 01.08.2014
comment
@ElderMael, я обновил свой ответ   -  person gipinani    schedule 01.08.2014
comment
@SotiriosDelimanolis, метод загружает объект, вызывающий соответствующий репозиторий   -  person gipinani    schedule 01.08.2014
comment
Менеджеры транзакций JPA и Hibernate работают почти одинаково... Я только что обновил свой ответ. В любом случае, если вы удалите аннотацию @Async, это должно сработать... но я считаю, что вы этого не хотите.   -  person ElderMael    schedule 01.08.2014
comment
@ElderMael Я знаю, что без асинхронности это работает :) Проблема в асинхронности;)   -  person gipinani    schedule 01.08.2014
comment
В этом случае вы можете реализовать CurrentSessionContext, что позволяет сохранять сеансы между потоками и вызывающими потоками.   -  person ElderMael    schedule 01.08.2014
comment
Это не так сложно, сложность в том, что вы будете управлять сессиями и потоками.   -  person ElderMael    schedule 01.08.2014
comment
@ElderMael Я посмотрю, спасибо!   -  person gipinani    schedule 01.08.2014
comment
@gipinani Я только что обновил вопрос с этой информацией.   -  person ElderMael    schedule 01.08.2014
comment
У меня была такая же проблема, когда я использовал @async. Я пробовал много вещей и, наконец, справился с этим, открыв новый сеанс и сделав что-то, а затем закрыв сеанс. Я не уверен, что это идеальное решение, но, учитывая тот факт, что сеанс гибернации и поток имеют некоторые личные проблемы при совместной работе, я доволен этим решением.   -  person Nihal Sharma    schedule 01.02.2015


Ответы (4)


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

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

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

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

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

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

person ElderMael    schedule 01.08.2014

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

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

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);
    }
}
person e.gunay    schedule 27.11.2014

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

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

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

person Xstian    schedule 01.08.2014
comment
Спасибо за ваш ответ. Мой метод уже транзакционный! - person gipinani; 01.08.2014

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

Если вы хотите использовать @Async вместе с ЛЕНИВОЙ загрузкой, метод, объявленный с помощью @Transactional, должен реализовать логику ЛЕНИВОЙ< /strong> загрузка.

Если ЛЕНИВАЯ загрузка инициирована вне @Transactional, она не будет работать.

person Laurynas    schedule 02.12.2019