1. 序章

この短いチュートリアルでは、「スレッドにバインドされたHibernateセッションがありません」例外がスローされるタイミングとその解決方法を明確にします。

ここでは、2つの異なるシナリオに焦点を当てます。

  1. LocalSessionFactoryBeanを使用する
  2. AnnotationSessionFactoryBeanを使用する

2. 原因

バージョン3では、Hibernateはコンテキストセッションの概念を導入し、 getCurrentSession()メソッドがSessionFactoryクラスに追加されました。 コンテキストセッションの詳細については、ここを参照してください。

Springには、 org.hibernate.context.CurrentSessionContext インターフェース– org.springframework.orm.hibernate3.SpringSessionContext (Spring Hibernate 3の場合)の独自の実装があります。 この実装では、セッションをトランザクションにバインドする必要があります。

当然、 getCurrentSession()メソッドを呼び出すクラスには、クラスレベルまたはメソッドレベルのいずれかで@Transactionalという注釈を付ける必要があります。 そうでない場合、 org.hibernate.HibernateException:スレッドにバインドされたHibernateセッションはスローされません。

例を簡単に見てみましょう。

3. LocalFactorySessionBean

彼は、この記事で取り上げる最初のシナリオです。

LocalSessionFactoryBeanを使用してJavaSpring構成クラスを定義します。

@Configuration
@EnableTransactionManagement
@PropertySource(
  { "classpath:persistence-h2.properties" }
)
@ComponentScan(
  { "com.baeldung.persistence.dao", "com.baeldung.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 )を使用していることに注意してください。 これは、org.springframework.orm.hibernate3.LocalSessionFactoryBeanがモデルクラスをマッピングするためのプロパティ 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;
    
    // ...
 }

以下のコードスニペットでわかるように、 SessionFactoryクラスのgetCurrentSession()メソッドを使用してHibernateセッションを取得します。

public abstract class AbstractHibernateDao<T extends Serializable> 
  implements IOperations<T> {
    private Class<T> 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.<Throwable>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<Event> events = service.findAll();
    }
}

4. AnnotationSessionFactoryBean

この例外は、org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBeanを使用してSpringアプリケーションでSessionFactoryを作成する場合にも発生する可能性があります。

これを示すサンプルコードを見てみましょう。 この点で、AnnotationSessionFactoryBeanを使用してJavaSpring構成クラスを定義します。

@Configuration
@EnableTransactionManagement
@PropertySource(
  { "classpath:persistence-h2.properties" }
)
@ComponentScan(
  { "com.baeldung.persistence.dao", "com.baeldung.persistence.service" }
)
public class PersistenceConfig {
    //...
    @Bean
    public AnnotationSessionFactoryBean sessionFactory() {
        AnnotationSessionFactoryBean sessionFactory 
          = new AnnotationSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(
          new String[] { "com.baeldung.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.<Throwable>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<Event> events = service.findAll();
    }
}

5. ソリューション

Springから取得したSessionFactorygetCurrentSession()メソッドは、開いているトランザクション内から呼び出す必要があることは明らかです。 したがって、解決策は、DAO/サービスメソッド/クラスに@Transactionalアノテーションが正しく付けられていることを確認することです。

Hibernate 4以降のバージョンでは、これと同じ理由でスローされる例外のメッセージの言い回しが異なることに注意してください。 「スレッドにバインドされたHibernateセッションがありません」の代わりに、現在のスレッドのトランザクション同期セッションを取得できませんでした」というメッセージが表示されます。

もう1つの重要なポイントがあります。 org.hibernate.context.CurrentSessionContext インターフェースに加えて、Hibernateは現在のセッションコンテキストを実装するクラスに設定できるプロパティhibernate.current_session_context_classを導入しました。

前に述べたように、Springにはこのインターフェースの独自の実装が付属しています。 SpringSessionContext。 デフォルトでは、 hibernate.current_session_context_class このクラスに等しいプロパティ。

結果として、このプロパティを他のプロパティに明示的に設定すると、Hibernateセッションとトランザクションを管理するSpringの機能が中断されます。 これも例外になりますが、検討中の例外とは異なります。

要約すると、Springを使用してHibernateセッションを管理する場合は、hibernate.current_session_context_classを明示的に設定しないでください。

6. 結論

この記事では、例外 org.hibernate.HibernateException:スレッドにバインドされたHibernateセッションがないがHibernate 3でスローされる理由と、サンプルコードおよびそれを簡単に解決する方法について説明しました。

この記事のコードは、Githubにあります。