1. 引言

本文将深入探讨 "No Hibernate Session Bound to Thread" 异常的触发场景及解决方案。我们将聚焦两种典型配置场景:

  1. 使用 LocalSessionFactoryBean
  2. 使用 AnnotationSessionFactoryBean

2. 异常根源

Hibernate 3 引入了上下文会话(contextual session)概念,并在 SessionFactory 类中新增了 getCurrentSession() 方法。Spring 提供了该接口的实现类 org.springframework.orm.hibernate3.SpringSessionContext该实现要求会话必须绑定到事务

⚠️ 关键点:调用 getCurrentSession() 的类必须添加 @Transactional 注解(类级别或方法级别),否则将抛出异常。

3. LocalFactorySessionBean 场景

3.1 配置示例

@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;
    }    
    // ...
}

💡 注意:LocalSessionFactoryBean 不支持 packagesToScan 属性,需通过 XML 配置文件映射实体类。

3.2 服务层实现

@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;
    
    // ...
 }

3.3 DAO 层实现

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

3.4 异常复现测试

@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"));
    }
}

3.5 正常场景测试

@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 场景

4.1 配置示例

@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;
    }
    // ...
}

4.2 异常复现测试

@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"));
    }
}

4.3 正常场景测试

@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. 解决方案

核心解决方案:确保调用 getCurrentSession() 的方法/类添加 @Transactional 注解。

📌 版本差异提示:

  • Hibernate 3 异常信息:No Hibernate Session Bound to Thread
  • Hibernate 4+ 异常信息:Could not obtain transaction-synchronized Session for current thread

⚠️ 避坑指南:切勿手动设置 hibernate.current_session_context_class 属性!Spring 会自动配置 SpringSessionContext,手动设置会破坏事务管理机制。

6. 总结

本文通过两种典型配置场景,解析了 Hibernate 3 中 No Hibernate Session Bound to Thread 异常的触发原因及解决方案。关键点总结:

  1. 必须在事务上下文中调用 getCurrentSession()
  2. 正确使用 @Transactional 注解
  3. 避免手动配置 hibernate.current_session_context_class

完整代码示例请参考:GitHub 仓库


» 下一篇: Java Weekly, 第201期