我有一个服务 Bean,它在实例化后将数据库中的对象加载到对象缓存中。如果我将调用我的 DAO 对象方法的服务方法标记为 @Transactional,则会收到"HibernateException:找不到当前线程的会话"错误。但是,如果我将 DAO 类标记为 @Transactional,则不会收到此类错误,并且它工作正常。
问题是我不能从服务对象中的同一方法进行多个 DAO 调用并将其作为一个事务。对可能导致这种情况的原因有什么想法吗?
我正在使用Spring 3.1和Hibernate 4。
DAO 示例:
@Repository
public class HibernateObjectDao implements ObjectDao {
SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public List<Object> getObjects() {
return sessionFactory.getCurrentSession()
.createQuery("from Object").list();
}
}
服务Bean示例:
@Service
public class MyServiceBean implements AbstractMyServiceBean
{
@Resource
private ObjectDao objectDao;
private HashMap<String,Object> objectCache;
public MyServiceBean() {
this.objectCache = new HashMap<String,Object>();
}
@Autowired
public void setObjectDao(ObjectDao objectDao) {
this.objectDao = objectDao;
}
@Transactional
public void initialize() {
loadObjectCache();
}
public void loadObjectCache() {
objectCache.put("stuff",this.objectDao.getObjects())
}
}
应用程序上下文.xml摘录:
<bean id="objectDao" class="com.example.persistence.HibernateObjectDao">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="myServiceBean"
class="com.example.service.MyServiceBean"
init-method="initialize">
<property name="objectDao" ref="objectDao" />
</bean>
当使用注入的 Bean 实例从 Bean 外部调用方法时,它们是事务性的,这实际上是围绕实际 Bean 实例的事务代理。
Spring 直接在 Bean 实例上调用 initialize 方法,而不是在事务代理上调用,因此不会在事务中调用这些方法。
将初始化方法放在另一个 Bean 中,该 Bean 将使用注入的 MyServiceBean 并调用其 initialize()
方法。
问题是当 Spring 调用初始化时,bean 周围没有事务代理。这是故意的,因为哲学是 bean 在初始化之前还没有准备好使用。
一些解决方案:
-
在这种初始时间数据处理的特殊情况下,通过 TransactionTemplate 或 session.beginTransaction() 进行手动事务处理。如果使用 JPA/Hibernate,请使用如下代码将 EntityManager 添加到事务同步器:
EntityManager em = entityManagerFactory.createEntityManager(); TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(em));
-
为应用程序中的所有初始化类型事件创建另一个 bean,并让它调用其他 bean。到那时,所有其他的 bean 都将准备就绪,Spring 将在它们周围放置一个事务代理。注意:不要从这个 bean 调用设置为 init 方法的方法,因为这样特定 bean 将被初始化两次,这并不总是健康的:)为此创建另一种方法。
-
使用应用程序侦听器。有了这个,你可以注册一个回调并将 ContextRefreshedEvent 处理为上下文初始化完成的标志。