tutorial servicio services restful ejemplo create crear java hibernate jersey guice jersey-2.0

servicio - hibernate java ejemplo



Contexto de persistencia de Hibernate basado en servidor host con Jersey (1)

No soy muy usuario de Guice, así que esta respuesta usa el framework DI de Jersey, HK2 . En un nivel básico de configuración, HK2 no es muy diferente de la configuración de Guice. Por ejemplo, con Guice tienes AbstractModule , donde HK2 tiene AbstractBinder . Con ambos componentes, usará un bind(..).to(..).in(Scope) Similar bind(..).to(..).in(Scope) sintaxis bind(..).to(..).in(Scope) . Una diferencia es que con Guice es bind(Contract).to(Impl) , mientras que con HK2 es bind(Impl).to(Contract) .

HK2 también tiene Factory s, que permiten una creación más compleja de sus objetos inyectables. Con sus fábricas, usaría la sintaxis bindFactory(YourFactory.class).to(YourContract.class) .

Dicho esto, podría implementar su caso de uso con algo como lo siguiente.

  1. Crear una Factory para English SessionFactory

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

  2. Crear una Factory para la SessionFactory francés

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

    Tenga en cuenta que las dos anteriores SessionFactory s estarán enlazadas en el ámbito de singleton y por nombre.

  3. Cree otra Factory que estará en un ámbito de solicitud, que hará uso de la información de contexto de solicitud. Esta fábrica inyectará las dos anteriores SessionFactory s por nombre (utilizando el enlace de nombre), y desde cualquier información de contexto de solicitud, devuelve la SessionFactory apropiada. El siguiente ejemplo simplemente usa un parámetro de consulta

    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. Entonces puedes simplemente inyectar SessionFactory (que daremos un nombre diferente) a tu dao.

    public class IDaoImpl implements IDao { private final SessionFactory sessionFactory; @Inject public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } }

  5. Para unir todo, usarás AbstractBinder similar a la siguiente implementación

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

    Aquí hay algunas cosas que debe tener en cuenta sobre la carpeta

    • Las dos SessionFactory específicas de un idioma diferente están vinculadas por su nombre. Que se utiliza para la inyección @Named , como puede ver en el paso 3.
    • La fábrica de ámbito de solicitud que toma la decisión también recibe un nombre.
    • Notará el proxy(true).proxyForSameScope(false) . Esto es necesario, ya que suponemos que el IDao será un singleton, y dado que el SessionFactory "elegido" estamos en un ámbito de solicitud, no podemos inyectar el SessionFactory real, ya que cambiará de solicitud a solicitud, por lo que necesitamos para inyectar un proxy. Si el IDao alcance de solicitud, en lugar de un singleton, podríamos omitir esas dos líneas. Sería mejor simplemente hacer que la solicitud de dao tenga un alcance, pero solo quería mostrar cómo se debe hacer como singleton.

      Consulte también Injecting Request Scoped Objects en Singleton Scoped Object con HK2 y Jersey , para obtener más información sobre este tema.

  6. Entonces solo necesitas registrar AbstractBinder con Jersey. Para eso, puedes usar el método de register(...) de ResourceConfig . Ver también , si requiere la configuración web.xml.

Eso es todo. A continuación se muestra una prueba completa utilizando Jersey Test Framework . Puede ejecutarlo como cualquier otra prueba JUnit. SessionFactory utilizado es solo una clase ficticia, no la Hibernate SessionFactory real. Es solo para mantener el ejemplo lo más corto posible, pero simplemente reemplácelo con su código de inicialización de 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)); } }

Otra cosa que también debería considerar es el cierre de SessionFactory s. Aunque la Factory tiene un método de dispose() , Jersey no la llama de manera confiable. Es posible que desee buscar en ApplicationEventListener . Puede inyectar SessionFactory s en él y cerrarlos en el evento close.

Tengo una aplicación de Jersey en ejecución escrita en Java utilizando Hibernate como implementación de JPA y utilizando Guice para vincular todos los servicios.

Mi caso de uso consiste en tener una instancia de aplicación que sirve múltiples localizaciones, disponible en diferentes hosts. Un ejemplo simple sería una versión en inglés y una versión en francés en application.com y application.fr . Dependiendo de qué host se active, necesitaría cambiar la aplicación para usar una base de datos diferente.

Actualmente, solo tengo una configuración de SessionFactory única que es utilizada por todos los objetos de acceso a datos, proporcionando acceso a una sola base de datos.

Estoy tratando de encontrar la forma más fácil de pasar la información sobre el contexto del país desde el recurso (donde puedo buscarlo desde el contexto de la solicitud) hasta el DAO, que necesita seleccionar uno de los múltiples SessionFactory .

Podría pasar un parámetro en cada método de servicio, pero eso parece muy tedioso. Pensé en usar un registro que tuviera una instancia de ThreadLocal del parámetro de país actual establecido por un filtro de Jersey, pero los locals locales se romperían al usar ejecutores, etc.

¿Hay alguna forma elegante de lograr esto?