Перейти к основному содержимому

HibernateException: сеанс Hibernate не привязан к потоку в Hibernate 3

· 5 мин. чтения

1. Введение

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

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

  1. с помощью LocalSessionFactoryBean
  2. с помощью AnnotationSessionFactoryBean

2. Причина

В версии 3 Hibernate представила концепцию контекстного сеанса, а метод getCurrentSession() был добавлен в класс SessionFactory . Более подробную информацию о контекстной сессии можно найти здесь .

В Spring есть собственная реализация интерфейса org.hibernate.context.CurrentSessionContextorg.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.foreach.persistence.dao", "com.foreach.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 для создания SessionFactory в нашем приложении Spring.

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

@Configuration
@EnableTransactionManagement
@PropertySource(
{ "classpath:persistence-h2.properties" }
)
@ComponentScan(
{ "com.foreach.persistence.dao", "com.foreach.persistence.service" }
)
public class PersistenceConfig {
//...
@Bean
public AnnotationSessionFactoryBean sessionFactory() {
AnnotationSessionFactoryBean sessionFactory
= new AnnotationSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[] { "com.foreach.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. Решение

Понятно, что метод getCurrentSession () SessionFactory , полученный из 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. Заключение

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

Код для этой статьи можно найти на Github .