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

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

1. Вступление

В этом коротком руководствеwe’ll clarify when “No Hibernate Session Bound to Thread” exception gets thrown and how to resolve it.

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

  1. используяLocalSessionFactoryBean

  2. используя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.