HibernateException: aucune session Hibernate liée à un thread dans Hibernate 3

HibernateException: aucune session Hibernate liée à un thread dans Hibernate 3

1. introduction

Dans ce court didacticiel,we’ll clarify when “No Hibernate Session Bound to Thread” exception gets thrown and how to resolve it.

Nous allons nous concentrer ici sur deux scénarios différents:

  1. en utilisant lesLocalSessionFactoryBean

  2. en utilisant lesAnnotationSessionFactoryBean

2. La cause

Avec la version 3, Hibernate a introduit le concept de session contextuelle et la méthodegetCurrentSession() a été ajoutée à la classeSessionFactory. Plus d'informations sur la session contextuelle peuvent être trouvéeshere.

Spring a sa propre implémentation de l'interfaceorg.hibernate.context.CurrentSessionContext -org.springframework.orm.hibernate3.SpringSessionContext (dans le cas de Spring Hibernate 3). __This implementation requires the session to be bound to a transaction.

Naturellement, les classes qui appellent la méthodegetCurrentSession() doivent être annotées avec@Transactional soit au niveau de la classe, soit au niveau de la méthode. Sinon, lesorg.hibernate.HibernateException: No Hibernate Session Bound to Thread seront lancés.

Jetons un coup d'œil à un exemple.

3. LocalFactorySessionBean __

C’est le premier scénario que nous examinerions dans cet article.

Nous allons définir une classe de configuration Java Spring avecLocalSessionFactoryBean:

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

Notez que nous utilisons ici un fichier de configuration Hibernate (exceptionDemo.cfg.xml) afin de mapper la classe de modèle. Cela est dû au fait quethe org.springframework.orm.hibernate3.LocalSessionFactoryBean does not provide the property*packagesToScan*, pour le mappage des classes de modèle.

Voici notre service simple:

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

    // ...
 }

Comme nous pouvons le voir dans l'extrait de code ci-dessous, la méthodegetCurrentSession() de la classeSessionFactory est utilisée pour obtenir la session 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();
    }
}

Le test ci-dessous réussit, démontrant comment l'exception sera levée lorsque la classeEventService contenant la méthode de service n'est pas annotée avec une annotation@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"));
    }
}

Ce test montre comment la méthode de service s'exécute avec succès lorsque la classeEventService est annotée avec l'annotation@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

Cette exception peut également se produire lorsque nous utilisons lesorg.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean pour créer lesSessionFactory dans notre application Spring.

Examinons un exemple de code qui illustre cela. Dans cette mesure, nous définissons une classe de configuration Java Spring avecAnnotationSessionFactoryBean:

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

Avec le même ensemble de classes DAO, Service et Model de la section précédente, nous rencontrons l'exception décrite ci-dessus:

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

Si nous annotons la classe de service avec une annotation@Transactional, la méthode de service fonctionne comme prévu et le test ci-dessous réussit:

@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. La solution

Il est clair que la méthodegetCurrentSession() desSessionFactory obtenus à partir de Spring doit être appelée depuis une transaction ouverte. Par conséquent,the solution is to ensure that our DAO/Service methods/classes are annotated correctly with the @Transactional annotation.

Il convient de noter que dans Hibernate 4 et les versions ultérieures, le message de l'exception qui est levé pour la même raison est libellé différemment. Au lieu des "No Hibernate Session Bound to Thread”,, nous obtiendrions"Could not obtain transaction-synchronized Session for current thread”.

Il y a un autre point important à souligner. En plus de l'interfaceorg.hibernate.context.CurrentSessionContext, Hibernate a introduit une propriétéhibernate.current_session_context_class qui peut être définie sur la classe qui implémente le contexte de session actuel.

Comme indiqué précédemment, Spring est livré avec sa propre implémentation de cette interface: leSpringSessionContext. Par défaut, il définit la propriétéhibernate.current_session_context_class égale à cette classe.

En conséquence, si nous définissons explicitement cette propriété sur autre chose, cela perturbe la capacité de Spring à gérer la session et les transactions Hibernate. Cela aboutit également à une exception, mais diffère de l'exception à l'étude.

Pour résumer, il est important de se rappeler que nous ne devons pas définir leshibernate.current_session_context_class explicitement lorsque nous utilisons Spring pour gérer la session Hibernate.

6. Conclusion

Dans cet article, nous avons examiné pourquoi et quand l'exceptionorg.hibernate.HibernateException: No Hibernate Session Bound to Thread est lancée dans Hibernate 3 avec un exemple de code et comment nous pouvons la résoudre facilement.

Le code de cet article se trouveover on Github.