Hibernate server-based persistence context with jersey

I have a Java application launcher application using Hibernate as the JPA implementation and using Guice to bind all the services.

My use case is that one application instance serves several localizations available under different hosts. A simple example would be the English version and the French version on application.com and application.fr . Depending on which host is running, I will need to switch the application to use a different database.

Currently, I have only one singleton SessionFactory configure, which is used by all data access objects, providing access to only one database.

I am trying to find the easiest way to transfer information about the country context, as they are, from a resource (where I can get it from the request context) to the DAO, which should select one of several SessionFactory s.

I can pass a parameter in every service method, but it seems very tedious. I was thinking about using a registry in which there would be a ThreadLocal instance of the current country parameter set by the Jersey filter, but stream locators could work with Executors, etc.

Are there any elegant ways to achieve this?

+5
source share
1 answer

I don't really like the Guice user, so this answer uses the Jersey DI, HK2 framework. At a basic configuration level, the HK2 is not much different from the Guice configuration. For example, Guice has an AbstractModule , where HK2 has an AbstractBinder . With both components, you will use the similar syntax bind(..).to(..).in(Scope) . One difference is that with Guice it is bind(Contract).to(Impl) , and with HK2 it is bind(Impl).to(Contract) .

HK2 also has Factory s, which allows for more complex creation of your injection objects. With your factories, you would use the syntax bindFactory(YourFactory.class).to(YourContract.class) .

In doing so, you can implement your use case with something like the following.

  • Create Factory for English SessionFactory

     public class EnglishSessionFactoryFactory implements Factory<SessionFactory> { @Override public SessionFactory provide() { ... } @Override public void dispose(SessionFactory t) {} } 
  • Create Factory for French SessionFactory

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

    Note that the above two SessionFactory will be bound in the Singleton area and by name.

  • Create another Factory that will be in the request area, which will use the request context information. This factory will enter the two above SessionFactory by name (using name binding) and from any request context information returns the corresponding SessionFactory . The example below just uses the query parameter

     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; } } 
  • Then you can simply enter a SessionFactory (which we will give a different name) in your dao.

     public class IDaoImpl implements IDao { private final SessionFactory sessionFactory; @Inject public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } } 
  • To bind everything together, you will use AbstractBinder , similar to the following implementation

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

    Here are some notes on the binder

    • Two different SessionFactory languages โ€‹โ€‹are related by name. Used for @Named injection, as you can see in step 3.
    • The request limited to the factory also indicates the name for decision.
    • You will notice proxy(true).proxyForSameScope(false) . This is necessary, since we assume that IDao will be single-point, and since the โ€œselectedโ€ SessionFactory is in the request area, we cannot enter the actual SessionFactory , since it will change from the request for the request, so we need to enter a proxy. If IDao was requested by scope rather than a single, then we could leave these two lines. It might be better to just make the dao query covered, but I just wanted to show how this should be done as a single.

      See also Objects with extended input objects in an object with a scope using Singleton with HK2 and Jersey , for a more detailed study of this topic.

      / li>
  • Then you just need to register AbstractBinder with Jersey. To do this, you can simply use the register(...) method for ResourceConfig . See also if you need web.xml customization.

What about that. Below is a complete test using the Jersey Test Framework . You can run it like any other JUnit test. The SessionFactory used is just a dummy class, not the actual Hibernate SessionFactory . This is only needed as short as possible, but just replace it with your regular Hibernate initialization code.

 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; /** * Qaru 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)); } } 

Another thing you can also consider is closing SessionFactory s. Although Factory has a dispose() method, it is not reliably called a Jersey. You can look at ApplicationEventListener . You can inject a SessionFactory into it and close them when closing.

+2
source

Source: https://habr.com/ru/post/1242250/


All Articles