HibernateException: нет сессии Hibernate, привязанной к потоку в Hibernate 3
1. Вступление
В этом коротком руководствеwe’ll clarify when “No Hibernate Session Bound to Thread” exception gets thrown and how to resolve it.
Здесь мы сосредоточимся на двух разных сценариях:
-
используяLocalSessionFactoryBean
-
используяAnnotationSessionFactoryBean
2. Причина
В версии 3 Hibernate представил концепцию контекстного сеанса, а методgetCurrentSession() был добавлен в классSessionFactory. Более подробную информацию о контекстном сеансе можно найти вhere.
Spring имеет собственную реализацию интерфейсаorg.hibernate.context.CurrentSessionContext -org.springframework.orm.hibernate3.SpringSessionContext (в случае Spring Hibernate 3). __This implementation requires the session to be bound to a transaction.с
Естественно, классы, которые вызывают методgetCurrentSession(), должны быть аннотированы@Transactional либо на уровне класса, либо на уровне метода. Если нет, будет брошенorg.hibernate.HibernateException: No Hibernate Session Bound to Thread.
Давайте быстро рассмотрим пример.
3. LocalFactorySessionBeanс __
Он первый сценарий, который мы рассмотрим в этой статье.
Мы определим класс конфигурации Java Spring с помощьюLocalSessionFactoryBean:
@Configuration
@EnableTransactionManagement
@PropertySource(
{ "classpath:persistence-h2.properties" }
)
@ComponentScan(
{ "org.example.persistence.dao", "org.example.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) для сопоставления класса модели. Это потому, чтоthe org.springframework.orm.hibernate3.LocalSessionFactoryBean does not provide the property*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
implements IOperations {
private Class 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.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 events = service.findAll();
}
}
4. AnnotationSessionFactoryBeanс
Это исключение также может возникнуть, когда мы используемorg.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean для созданияSessionFactory в нашем приложении Spring.
Давайте посмотрим на пример кода, который это демонстрирует. В этой связи мы определяем класс конфигурации Java Spring с помощьюAnnotationSessionFactoryBean:
@Configuration
@EnableTransactionManagement
@PropertySource(
{ "classpath:persistence-h2.properties" }
)
@ComponentScan(
{ "org.example.persistence.dao", "org.example.persistence.service" }
)
public class PersistenceConfig {
//...
@Bean
public AnnotationSessionFactoryBean sessionFactory() {
AnnotationSessionFactoryBean sessionFactory
= new AnnotationSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[] { "org.example.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.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 events = service.findAll();
}
}
5. Решение
Понятно, что методgetCurrentSession() дляSessionFactory, полученного из Spring, необходимо вызывать из открытой транзакции. Следовательно,the solution is to ensure that our DAO/Service methods/classes are annotated correctly with the @Transactional annotation.
Следует отметить, что в 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 и транзакциями. Это также приводит к исключению, но отличается от рассматриваемого исключения.
Подводя итог, важно помнить, что мы не должны явно устанавливатьhibernate.current_session_context_class, когда мы используем Spring для управления сеансом Hibernate.
6. Заключение
В этой статье мы рассмотрели, почему возникает исключениеorg.hibernate.HibernateException: No Hibernate Session Bound to Thread в Hibernate 3 вместе с некоторыми примерами кода, и как мы можем легко решить эту проблему.
Код для этой статьи можно найтиover on Github.