1. Введение
В этом кратком руководстве мы объясним, когда возникает исключение «Нет сеанса гибернации, привязанного к потоку», и как его разрешить.
Здесь мы сосредоточимся на двух разных сценариях:
- с помощью
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
Это первый сценарий, который мы рассмотрим в этой статье.
Мы определим класс конфигурации 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 .