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.
Crear una
Factory
para EnglishSessionFactory
public class EnglishSessionFactoryFactory implements Factory<SessionFactory> { @Override public SessionFactory provide() { ... } @Override public void dispose(SessionFactory t) {} }
Crear una
Factory
para laSessionFactory
francéspublic 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.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 anterioresSessionFactory
s por nombre (utilizando el enlace de nombre), y desde cualquier información de contexto de solicitud, devuelve laSessionFactory
apropiada. El siguiente ejemplo simplemente usa un parámetro de consultapublic 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; } }
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; } }
Para unir todo, usarás
AbstractBinder
similar a la siguiente implementaciónpublic 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 elIDao
será un singleton, y dado que elSessionFactory
"elegido" estamos en un ámbito de solicitud, no podemos inyectar elSessionFactory
real, ya que cambiará de solicitud a solicitud, por lo que necesitamos para inyectar un proxy. Si elIDao
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.
- Las dos
Entonces solo necesitas registrar
AbstractBinder
con Jersey. Para eso, puedes usar el método deregister(...)
deResourceConfig
. 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?