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:
-
usando oLocalSessionFactoryBean
-
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.