«1. Введение

В этом кратком руководстве мы объясним, когда возникает исключение «Нет Hibernate Session Bound to Thread» и как его разрешить.

Здесь мы сосредоточимся на двух разных сценариях:

  1. using the LocalSessionFactoryBean
  2. using the 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

Это первый сценарий, который мы рассмотрим в этой статье.

Мы определим класс конфигурации Java Spring с LocalSessionFactoryBean:

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

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

Как видно из фрагмента кода ниже, метод getCurrentSession() класса SessionFactory используется для получения сеанса Hibernate:

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

Этот тест показывает, как метод службы успешно выполняется, когда класс EventService аннотирован с аннотацией @Transactional:

4. AnnotationSessionFactoryBean

Это исключение также может возникнуть, когда мы используем org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean для создания SessionFactory в нашем приложении 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;
    }
    // ...
}

Давайте посмотрим на пример кода, демонстрирующий это. Для этого мы определяем класс конфигурации Java Spring с помощью AnnotationSessionFactoryBean:

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

С тем же набором классов 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 whenEntityIsCreated_thenNoExceptions() {
        service.create(new Event("from AnnotationSessionFactoryBean"));
        List<Event> events = service.findAll();
    }
}

Если мы аннотируем класс службы аннотацией @Transactional, метод службы работает должным образом, и тест, показанный ниже, проходит успешно:

5. Решение

Понятно, что метод getCurrentSession() класса SessionFactory получил from Spring необходимо вызывать из открытой транзакции. Поэтому решение состоит в том, чтобы убедиться, что наши методы/классы DAO/Service правильно аннотированы аннотацией @Transactional.

Следует отметить, что в Hibernate 4 и более поздних версиях сообщение об исключении, которое выдается по той же причине, сформулировано иначе. Вместо «Нет сеанса гибернации, привязанного к потоку», мы получим «Не удалось получить сеанс, синхронизированный с транзакциями, для текущего потока».

Есть еще один важный момент. Наряду с интерфейсом org.hibernate.context.CurrentSessionContext в Hibernate появилось свойство hibernate.current_session_context_class, которое может быть установлено в класс, реализующий текущий контекст сеанса.

Как было сказано ранее, Spring поставляется со своей собственной реализацией этого интерфейса: SpringSessionContext. По умолчанию он устанавливает свойство hibernate.current_session_context_class равным этому классу.

Как следствие, если мы явно установим для этого свойства что-то другое, это нарушит способность Spring управлять сеансом Hibernate и транзакциями. Это также приводит к исключению, но отличается от рассматриваемого исключения.

«Подводя итог, важно помнить, что мы не должны явно устанавливать hibernate.current_session_context_class, когда используем Spring для управления сеансом Hibernate.

6. Заключение

В этой статье мы рассмотрели, почему исключение org.hibernate.HibernateException: No Hibernate Session Bound to Thread вызывается в Hibernate 3 вместе с некоторым примером кода и как мы можем легко решить эту проблему. .