2016-02-03 10 views
5

JPA実装としてHibernateを使用し、Guiceを使用してすべてのサービスを一緒にバインドするJavaで記述された実行中のJerseyアプリケーションがあります。Jerseyを使用したサーバーホストに基づくHibernate永続コンテキスト

私の使用例は、複数のローカライズを提供する1つのアプリケーションインスタンスを別のホストで使用できることにあります。簡単な例は、英語版とフランス語版application.comapplication.frです。どのホストが起動されたかによって、別のデータベースを使用するようにアプリケーションを切り替える必要があります。

現在のところ、1つのシングルトンしか持たず、すべてのデータアクセスオブジェクトで使用される1つのデータベースにのみアクセスできます。

私は、(要求コンテキストから取り出すことができる)リソースから国のコンテキストに関するすべての情報をDAOに渡す最も簡単な方法を考え出しています。複数のSessionFactoryがあります。

私はすべてのサービスメソッドでパラメータを渡すことができますが、それは非常に面倒です。私は、ジャージーフィルタによって現在の国のパラメータセットのThreadLocalインスタンスを持つレジストリを使用すると考えましたが、エグゼキュータなどを使用してスレッドローカルが壊れてしまいます。

これを実現する方法はありますか?

+0

どのジャージーバージョンを使用していますか? –

+0

ジャージー2.19を使用しています。未成年者を時々更新します – vvondra

答えて

2

私はGuiceのユーザーではないので、この回答はJerseyのDIフレームワークHK2を使用しています。基本的な設定レベルでは、HK2はGuiceの設定とあまり変わりません。たとえばGuiceの場合、AbstractModuleがあり、HK2はAbstractBinderです。どちらのコンポーネントでも、同様の構文のbind(..).to(..).in(Scope)を使用します。 1つの違いは、Guiceの場合はbind(Contract).to(Impl)、HK2の場合はbind(Impl).to(Contract)です。

HK2にはFactoryもあり、注入可能なオブジェクトのより複雑な作成を可能にします。あなたの工場では、構文bindFactory(YourFactory.class).to(YourContract.class)を使用します。

これは、次のような方法でユースケースを実装できます。

  1. 上記2つのSessionFactory sがシングルトンスコープでかつによってバインドされます注意してくださいフランスのSessionFactory

    public class FrenchSessionFactoryFactory implements Factory<SessionFactory> { 
        @Override 
        public SessionFactory provide() { 
         ... 
        } 
        @Override 
        public void dispose(SessionFactory t) {}  
    } 
    

    ためFactoryを作成し、英語SessionFactory

    public class EnglishSessionFactoryFactory implements Factory<SessionFactory> { 
        @Override 
        public SessionFactory provide() { 
         ... 
        } 
        @Override 
        public void dispose(SessionFactory t) {} 
    } 
    
  2. ためFactoryを作成します。名。

  3. 別のFactoryを作成してリクエストスコープに入れると、要求コンテキスト情報が使用されます。このファクトリは、上記の2つの名前を(名前バインディングを使用して)名前で入力し、要求コンテキスト情報から適切なSessionFactoryを返します。単にクエリパラメータ

    public class SessionFactoryFactory 
         extends AbstractContainerRequestValueFactory<SessionFactory> { 
    
        @Inject 
        @Named("EnglishSessionFactory") 
        private SessionFactory englishSessionFactory; 
    
        @Inject 
        @Named("FrenchSessionFactory") 
        private SessionFactory frenchSessionFactory; 
    
        @Override 
        public SessionFactory provide() { 
         ContainerRequest request = getContainerRequest(); 
         String lang = request.getUriInfo().getQueryParameters().getFirst("lang"); 
         if (lang != null && "fr".equals(lang)) { 
          return frenchSessionFactory; 
         } 
         return englishSessionFactory; 
        } 
    } 
    
  4. を使用する例を下回る次に、あなただけのあなたのDAOに(私たちは別の名前を与えるであろう)SessionFactoryを注入することができます。一緒にすべてをバインドする

    public class IDaoImpl implements IDao { 
    
        private final SessionFactory sessionFactory; 
    
        @Inject 
        public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) { 
         this.sessionFactory = sessionFactory; 
        } 
    } 
    
  5. 、あなたはここで次の実装

    public class PersistenceBinder extends AbstractBinder { 
    
        @Override 
        protected void configure() { 
         bindFactory(EnglishSessionFactoryFactory.class).to(SessionFactory.class) 
           .named("EnglishSessionFactory").in(Singleton.class); 
         bindFactory(FrenchSessionFactoryFactory.class).to(SessionFactory.class) 
           .named("FrenchSessionFactory").in(Singleton.class); 
         bindFactory(SessionFactoryFactory.class) 
           .proxy(true) 
           .proxyForSameScope(false) 
           .to(SessionFactory.class) 
           .named("SessionFactory") 
           .in(RequestScoped.class); 
         bind(IDaoImpl.class).to(IDao.class).in(Singleton.class); 
        } 
    } 
    

    に似AbstractBinderは、バインダー

    • 二つの異なる言語の特定について注意すべきいくつかのものです使用します。 SessionFactoryは名前でバインドされています。ステップ3で確認できるように、@Named注入に使用されます。
    • 決定を行う要求スコープ付きファクトリにも名前が付けられています。
    • proxy(true).proxyForSameScope(false)があります。これは、IDaoがシングルトンになると仮定し、 "選択された" SessionFactoryがリクエストスコープにあると仮定しているため、リクエストからリクエストに変更されるため、実際にSessionFactoryを挿入することはできません。プロキシを注入する。 IDaoがシングルトンではなくリクエストスコープだった場合、その2つの行を省略することができます。 daoリクエストをスコープにするほうが良いかもしれませんが、シングルトンとしてどのように行うべきかを示したかっただけです。

      このトピックの詳細については、Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jerseyも参照してください。

  6. 次に、あなただけのジャージーでAbstractBinderを登録する必要があります。そのためには、のregister(...)メソッドを使用してください。 See also、web.xml設定が必要な場合。

これはそれです。以下はJersey Test Frameworkを使った完全なテストです。他のJUnitテストと同様に実行できます。 SessionFactoryは実際の休止状態SessionFactoryではなく、ダミークラスです。できるだけ短くしておくだけですが、通常のHibernate初期化コードに置き換えてください。

import java.util.logging.Logger; 
import javax.inject.Inject; 
import javax.inject.Named; 
import javax.inject.Singleton; 
import javax.ws.rs.GET; 
import javax.ws.rs.Path; 
import javax.ws.rs.core.Response; 
import javax.ws.rs.ext.ExceptionMapper; 

import org.glassfish.hk2.api.Factory; 
import org.glassfish.hk2.utilities.binding.AbstractBinder; 
import org.glassfish.jersey.filter.LoggingFilter; 
import org.glassfish.jersey.process.internal.RequestScoped; 
import org.glassfish.jersey.server.ContainerRequest; 
import org.glassfish.jersey.server.ResourceConfig; 
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory; 
import org.glassfish.jersey.test.JerseyTest; 
import org.junit.Test; 

import static junit.framework.Assert.assertEquals; 

/** 
* Stack Overflow https://stackoverflow.com/q/35189278/2587435 
* 
* Run this like any other JUnit test. There is only one required dependency 
* 
* <dependency> 
*  <groupId>org.glassfish.jersey.test-framework.providers</groupId> 
*  <artifactId>jersey-test-framework-provider-inmemory</artifactId> 
*  <version>${jersey2.version}</version> 
*  <scope>test</scope> 
* </dependency> 
* 
* @author Paul Samsotha 
*/ 
public class SessionFactoryContextTest extends JerseyTest { 

    public static interface SessionFactory { 
     Session openSession(); 
    } 

    public static class Session { 
     private final String language; 
     public Session(String language) { 
      this.language = language; 
     } 
     public String get() { 
      return this.language; 
     } 
    } 

    public static class EnglishSessionFactoryFactory implements Factory<SessionFactory> { 
     @Override 
     public SessionFactory provide() { 
      return new SessionFactory() { 
       @Override 
       public Session openSession() { 
        return new Session("English"); 
       } 
      }; 
     } 

     @Override 
     public void dispose(SessionFactory t) {}  
    } 

    public static class FrenchSessionFactoryFactory implements Factory<SessionFactory> { 
     @Override 
     public SessionFactory provide() { 
      return new SessionFactory() { 
       @Override 
       public Session openSession() { 
        return new Session("French"); 
       } 
      }; 
     } 

     @Override 
     public void dispose(SessionFactory t) {}  
    } 

    public static class SessionFactoryFactory 
      extends AbstractContainerRequestValueFactory<SessionFactory> { 

     @Inject 
     @Named("EnglishSessionFactory") 
     private SessionFactory englishSessionFactory; 

     @Inject 
     @Named("FrenchSessionFactory") 
     private SessionFactory frenchSessionFactory; 

     @Override 
     public SessionFactory provide() { 
      ContainerRequest request = getContainerRequest(); 
      String lang = request.getUriInfo().getQueryParameters().getFirst("lang"); 
      if (lang != null && "fr".equals(lang)) { 
       return frenchSessionFactory; 
      } 
      return englishSessionFactory; 
     } 
    } 

    public static interface IDao { 
     public String get(); 
    } 

    public static class IDaoImpl implements IDao { 

     private final SessionFactory sessionFactory; 

     @Inject 
     public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) { 
      this.sessionFactory = sessionFactory; 
     } 

     @Override 
     public String get() { 
      return sessionFactory.openSession().get(); 
     } 
    } 

    public static class PersistenceBinder extends AbstractBinder { 

     @Override 
     protected void configure() { 
      bindFactory(EnglishSessionFactoryFactory.class).to(SessionFactory.class) 
        .named("EnglishSessionFactory").in(Singleton.class); 
      bindFactory(FrenchSessionFactoryFactory.class).to(SessionFactory.class) 
        .named("FrenchSessionFactory").in(Singleton.class); 
      bindFactory(SessionFactoryFactory.class) 
        .proxy(true) 
        .proxyForSameScope(false) 
        .to(SessionFactory.class) 
        .named("SessionFactory") 
        .in(RequestScoped.class); 
      bind(IDaoImpl.class).to(IDao.class).in(Singleton.class); 
     } 
    } 

    @Path("test") 
    public static class TestResource { 

     private final IDao dao; 

     @Inject 
     public TestResource(IDao dao) { 
      this.dao = dao; 
     } 

     @GET 
     public String get() { 
      return dao.get(); 
     } 
    } 

    private static class Mapper implements ExceptionMapper<Throwable> { 
     @Override 
     public Response toResponse(Throwable ex) { 
      ex.printStackTrace(System.err); 
      return Response.serverError().build(); 
     } 
    } 

    @Override 
    public ResourceConfig configure() { 
     return new ResourceConfig(TestResource.class) 
       .register(new PersistenceBinder()) 
       .register(new Mapper()) 
       .register(new LoggingFilter(Logger.getAnonymousLogger(), true)); 
    } 

    @Test 
    public void shouldReturnEnglish() { 
     final Response response = target("test").queryParam("lang", "en").request().get(); 
     assertEquals(200, response.getStatus()); 
     assertEquals("English", response.readEntity(String.class)); 
    } 

    @Test 
    public void shouldReturnFrench() { 
     final Response response = target("test").queryParam("lang", "fr").request().get(); 
     assertEquals(200, response.getStatus()); 
     assertEquals("French", response.readEntity(String.class)); 
    } 
} 

もう1つのことは、SessionFactoryのクローズです。 Factoryにはdispose()メソッドがありますが、確実にJerseyによって呼び出されるわけではありません。 ApplicationEventListenerを調べることができます。 SessionFactoryをその中に注入して、クローズイベントでそれらをシャットダウンすることができます。

+0

時間をいただき、ありがとうございます! – vvondra

+0

素晴らしい!特に 'プロキシ(真).proxyForSameScope(偽) 'を指摘するために。 – vvondra

+0

@wondraも参照してくださいhttp://stackoverflow.com/q/35994965/2587435。これにより、メインのセッションファクトリに追加の名前を使用する必要はありません。あなたは名前なしでそれを注入することができます。ちょうど何か新しいことを学んだ:-) –

関連する問題