1. 簡介
本教程將闡明“未綁定線程的 Hibernate 會話”異常何時被拋出以及如何解決它。
我們將重點關注以下兩個場景:
- 使用LocalSessionFactoryBean
- 使用AnnotationSessionFactoryBean
2. 根本原因
在版本 3 中,Hibernate 引入了上下文會話的概念,並且為 getCurrentSession() 方法添加到了 SessionFactory 類中。有關上下文會話的更多信息,請參見 這裏。Spring 自身實現了 org.hibernate.context.CurrentSessionContext 接口——org.springframework.orm.hibernate3.SpringSessionContext (在 Spring Hibernate 3 的情況下)。該實現要求會話與事務綁定。
自然地,調用 getCurrentSession() 方法的類應在類級別或方法級別上使用 @Transactional 註解。如果未這樣做,則會拋出 org.hibernate.HibernateException: No Hibernate Session Bound to Thread 異常。
讓我們來看一個示例。
3. LocalFactorySessionBean
這是我們在本文檔中首先要考慮的場景。
我們將定義一個帶有 LocalSessionFactoryBean 的 Java Spring 配置類:
@Configuration
@EnableTransactionManagement
@PropertySource(
{ "classpath:persistence-h2.properties" }
)
@ComponentScan(
{ "com.baeldung.persistence.dao", "com.baeldung.persistence.service" }
)
public class PersistenceConfigHibernate3 {
// ...
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory
= new LocalSessionFactoryBean();
Resource config = new ClassPathResource("exceptionDemo.cfg.xml");
sessionFactory.setDataSource(dataSource());
sessionFactory.setConfigLocation(config);
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
// ...
}請注意,我們在此使用 Hibernate 配置文件 (exceptionDemo.cfg.xml) 以進行模型類的映射。這是因為 org.springframework.orm.hibernate3.LocalSessionFactoryBean 不提供 packagesToScan 屬性,用於映射模型類。
以下是我們的簡單服務:
@Service
@Transactional
public class EventService {
@Autowired
private IEventDao dao;
public void create(Event entity) {
dao.create(entity);
}
}@Entity
@Table(name = "EVENTS")
public class Event implements Serializable {
@Id
@GeneratedValue
private Long id;
private String description;
// ...
}如代碼片段所示,getCurrentSession() 方法是用於從 SessionFactory 類中獲取 Hibernate 會話的。
public abstract class AbstractHibernateDao<T extends Serializable>
implements IOperations<T> {
private Class<T> clazz;
@Autowired
private SessionFactory sessionFactory;
// ...
@Override
public void create(T entity) {
Preconditions.checkNotNull(entity);
getCurrentSession().persist(entity);
}
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}以下測試通過,展示了當類 EventService 缺少 @Transactional 註解時,異常將會被拋出的情況:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = { PersistenceConfigHibernate3.class },
loader = AnnotationConfigContextLoader.class
)
public class HibernateExceptionScen1MainIntegrationTest {
@Autowired
EventService service;
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void whenNoTransBoundToSession_thenException() {
expectedEx.expectCause(
IsInstanceOf.<Throwable>instanceOf(HibernateException.class));
expectedEx.expectMessage("No Hibernate Session bound to thread, "
+ "and configuration does not allow creation "
+ "of non-transactional one here");
service.create(new Event("from LocalSessionFactoryBean"));
}
}此測試展示了當 EventService 類被註解為 @Transactional 註解時,服務方法成功執行的方式:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = { PersistenceConfigHibernate3.class },
loader = AnnotationConfigContextLoader.class
)
public class HibernateExceptionScen1MainIntegrationTest {
@Autowired
EventService service;
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void whenEntityIsCreated_thenNoExceptions() {
service.create(new Event("from LocalSessionFactoryBean"));
List<Event> events = service.findAll();
}
}4. AnnotationSessionFactoryBean
這個異常也可能在我們使用 org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean 來在我們的 Spring 應用中創建 SessionFactory 時發生。
讓我們來看一些演示此現象的示例代碼。為此,我們定義了一個使用 AnnotationSessionFactoryBean 的 Java Spring 配置類:
@Configuration
@EnableTransactionManagement
@PropertySource(
{ "classpath:persistence-h2.properties" }
)
@ComponentScan(
{ "com.baeldung.persistence.dao", "com.baeldung.persistence.service" }
)
public class PersistenceConfig {
//...
@Bean
public AnnotationSessionFactoryBean sessionFactory() {
AnnotationSessionFactoryBean sessionFactory
= new AnnotationSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[] { "com.baeldung.persistence.model" });
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
// ...
}使用與上一節相同的 DAO、Service 和 Model 類集,我們遇到上面描述的異常:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = { PersistenceConfig.class },
loader = AnnotationConfigContextLoader.class
)
public class HibernateExceptionScen2MainIntegrationTest {
@Autowired
EventService service;
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void whenNoTransBoundToSession_thenException() {
expectedEx.expectCause(
IsInstanceOf.<Throwable>instanceOf(HibernateException.class));
expectedEx.expectMessage("No Hibernate Session bound to thread, "
+ "and configuration does not allow creation "
+ "of non-transactional one here");
service.create(new Event("from AnnotationSessionFactoryBean"));
}
}如果我們將服務類註解為 @Transactional 註解,則服務方法按預期工作,並且下面的測試通過。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
classes = { PersistenceConfig.class },
loader = AnnotationConfigContextLoader.class
)
public class HibernateExceptionScen2MainIntegrationTest {
@Autowired
EventService service;
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void whenEntityIsCreated_thenNoExceptions() {
service.create(new Event("from AnnotationSessionFactoryBean"));
List<Event> events = service.findAll();
}
}5. 解決方案
顯然,從 Spring 獲取的 SessionFactory 中的 getCurrentSession() 方法必須在打開的事務中調用。因此,解決方案是確保我們的 DAO/Service 方法/類正確地使用 @Transactional 註解。
需要注意的是,在 Hibernate 4 及更高版本中,由於相同原因拋出的異常消息有所不同。我們不再得到“No Hibernate Session Bound to Thread”,而是得到“Could not obtain transaction-synchronized Session for current thread”。
還有另一個重要的要點。 隨着 org.hibernate.context.CurrentSessionContext 接口的引入,Hibernate 還引入了 hibernate.current_session_context_class 屬性,該屬性可以設置為當前會話上下文的實現類。
正如之前所述,Spring 提供了該接口的自身實現:SpringSessionContext。 默認情況下,它會將 hibernate.current_session_context_class 屬性設置為該類。
因此,如果我們顯式地將此屬性設置為其他值,則會干擾 Spring 管理 Hibernate 會話和事務的能力,從而導致另一個異常,但與所考慮的異常不同。
總結一下,當我們使用 Spring 管理 Hibernate 會話時,我們不應顯式地設置 hibernate.current_session_context_class 屬性。
6. 結論
在本文中,我們探討了當異常org.hibernate.HibernateException: No Hibernate Session Bound to Thread在 Hibernate 3 中被拋出時的原因,以及如何通過示例代碼輕鬆解決它。