HibernateException: Nenhuma sessão de hibernação vinculada ao thread no Hibernate 3

HibernateException: Nenhuma sessão de hibernação vinculada ao thread no Hibernate 3

1. Introdução

Neste breve tutorial,we’ll clarify when “No Hibernate Session Bound to Thread” exception gets thrown and how to resolve it.

Vamos nos concentrar aqui em dois cenários diferentes:

  1. usando oLocalSessionFactoryBean

  2. usando oAnnotationSessionFactoryBean

2. A causa

Com a versão 3, o Hibernate introduziu o conceito de sessão contextual e o métodogetCurrentSession() foi adicionado à classeSessionFactory. Mais informações sobre a sessão contextual podem ser encontradashere.

O Spring tem sua própria implementação da interfaceorg.hibernate.context.CurrentSessionContext -org.springframework.orm.hibernate3.SpringSessionContext (no caso do Spring Hibernate 3). __This implementation requires the session to be bound to a transaction.

Naturalmente, as classes que chamam o métodogetCurrentSession() devem ser anotadas com@Transactional no nível da classe ou no nível do método. Caso contrário, oorg.hibernate.HibernateException: No Hibernate Session Bound to Thread será lançado.

Vamos dar uma olhada rápida em um exemplo.

3. LocalFactorySessionBean __

Ele é o primeiro cenário que veríamos neste artigo.

Vamos definir uma classe de configuração Java Spring comLocalSessionFactoryBean:

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

Observe que usamos um arquivo de configuração do Hibernate (exceptionDemo.cfg.xml) aqui para mapear a classe do modelo. Isso ocorre porquethe org.springframework.orm.hibernate3.LocalSessionFactoryBean does not provide the property*packagesToScan*, para classes de modelo de mapeamento.

Aqui está nosso serviço simples:

@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;

    // ...
 }

Como podemos ver no trecho de código abaixo, o métodogetCurrentSession() da classeSessionFactory é usado para obter a sessão do 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();
    }
}

O teste abaixo foi aprovado, demonstrando como a exceção será lançada quando a classeEventService contendo o método de serviço não for anotada com uma anotação@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"));
    }
}

Este teste mostra como o método de serviço é executado com sucesso quando a classeEventService é anotada com a anotação@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

Essa exceção também pode ocorrer quando usamosorg.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean para criarSessionFactory em nosso aplicativo Spring.

Vejamos alguns exemplos de código que demonstram isso. Nesse sentido, definimos uma classe de configuração Java Spring comAnnotationSessionFactoryBean:

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

Com o mesmo conjunto de classes DAO, Serviço e Modelo da seção anterior, encontramos a exceção descrita acima:

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

Se anotarmos a classe de serviço com uma anotação@Transactional, o método de serviço funciona conforme o esperado e o teste mostrado abaixo passa:

@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. A solução

É claro que o métodogetCurrentSession() deSessionFactory obtido do Spring precisa ser chamado de dentro de uma transação aberta. Portanto,the solution is to ensure that our DAO/Service methods/classes are annotated correctly with the @Transactional annotation.

Deve-se observar que no Hibernate 4 e versões posteriores, a mensagem da exceção lançada pelo mesmo motivo tem palavras diferentes. Em vez de “No Hibernate Session Bound to Thread”,, obteríamos“Could not obtain transaction-synchronized Session for current thread”.

Há outro ponto importante a ser feito. Junto com a interfaceorg.hibernate.context.CurrentSessionContext, o Hibernate introduziu uma propriedadehibernate.current_session_context_class que pode ser configurada para a classe que implementa o contexto da sessão atual.

Como afirmado antes, o Spring vem com sua própria implementação dessa interface: oSpringSessionContext. Por padrão, ele define a propriedadehibernate.current_session_context_class igual a esta classe.

Como consequência, se definirmos explicitamente essa propriedade para outra coisa, isso interrompe a capacidade do Spring de gerenciar a sessão e as transações do Hibernate. Isso resulta em uma exceção também, mas é diferente da exceção em consideração.

Resumindo, é importante lembrar que não devemos definir ohibernate.current_session_context_class explicitamente quando usamos o Spring para gerenciar a sessão do Hibernate.

6. Conclusão

Neste artigo, vimos por que quando a exceçãoorg.hibernate.HibernateException: No Hibernate Session Bound to Thread é lançada no Hibernate 3 junto com algum código de exemplo e como podemos resolvê-lo facilmente.

O código para este artigo pode ser encontradoover on Github.