I'm using an @Async
annotation on a service layer method.
Everything works fine when I EAGERLY load @OneToMany collection fields, but when I try to access LAZY loaded element I found that Hibernate SessionImplementor
object session
is null. That obviously give me an exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role:
....
Here is my collection field:
@OneToMany(mappedBy="abc", fetch=FetchType.LAZY, cascade=CascadeType.REMOVE)
@OrderBy(value="xsd asc")
@JsonIgnore
private Set<Item> items = new HashSet<Item>();
How can I bind hibernate session in order to LAZELY load my object inside @Async
context?
EDIT
Here is my trancactionManager / entityManager configuration
<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"/>
Spring's transaction context is preserved using ThreadLocals. This means that your SessionFactory is only available to the thread dispatching your request thus, if you create a new thread, you will get a null
and a corresponding exception.
What your @Async
method does is use a TaskExecutor to run your method in another thread. So the problem described above is happening with your service.
I quote from the Spring's JpaTransactionManager docs:
PlatformTransactionManager implementation for a single JPA EntityManagerFactory. Binds a JPA EntityManager from the specified factory to the thread, potentially allowing for one thread-bound EntityManager per factory. SharedEntityManagerCreator and @PersistenceContext are aware of thread-bound entity managers and participate in such transactions automatically. Using either is required for JPA access code supporting this transaction management mechanism.
If you want to preserve your annotation then you should take a look at Hibernate CurrentSessionContext and somehow manage the sessions yourself.
See this question for more info.
I had the same problem, spent few days trying to find a solution, finally got a solution. I would like to share the details I found for those who might have the same issue.
1st - Your @Async
-annotated method should be declared in a separate bean rather than the @Controller
- or @RestController
-annotated bean.
2nd - You certainly need to declare the method @Transactional
which is called from within @Async
declared method. However, the very first method called from @Async
method has to be defined @Transactional
. I had the @Transactional
method in second or third level in the method execution stack therefore the problem was not solved and I spent two days trying to figure out what was going on.
So the best thing to do is
@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);
}
}
In normal circumstances (without @Async
) a transaction gets propagated through the call hierarchy from one Spring component to the other.
When a @Transactional
Spring @Component
calls a method annotated with @Async
this does not happen. The call to the asynchronous method is being scheduled and executed at a later time by a task executor and is thus handled as a 'fresh' call, i.e. without a transactional context. If the @Async
method (or the component in which it is declared) is not @Transactional
by itself Spring will not manage any needed transactions.
Try to annotate the method that calls the @Async
method, and tell us if worked.
来源:https://stackoverflow.com/questions/25083295/spring-async-null-hibernate-session-on-lazy-collection