java - 基于带有 Jersey 的服务器主机的 Hibernate 持久性上下文

标签 java hibernate jersey guice jersey-2.0

我有一个运行中的 Jersey 应用程序,用 Java 编写,使用 Hibernate 作为 JPA 实现,并使用 Guice 将所有服务绑定(bind)在一起。

我的用例是让一个应用程序实例服务于多个本地化,在不同的主机下可用。简单示例是 application.comapplication.fr 上的英文版和法文版。根据触发的主机,我需要切换应用程序以使用不同的数据库。

目前,我只有一个单例SessionFactory 配置,它被所有数据访问对象使用,只提供对一个数据库的访问。

我正在尝试想出最简单的方法来将有关国家/地区上下文的信息从资源(我可以从请求上下文中获取它)一路传递到 DAO,DAO 需要从多个选项中选择一个SessionFactory

我可以在每个服务方法中传递一个参数,但这看起来很乏味。我想过使用一个注册表,它有一个由 Jersey 过滤器设置的当前国家/地区参数的 ThreadLocal 实例,但是线程局部变量会在使用 Executors 等时中断。

有什么优雅的方法可以做到这一点吗?

最佳答案

我不是 Guice 用户,所以这个答案使用 Jersey 的 DI 框架,HK2 .在基本配置层面,HK2 与 Guice 配置没有太大区别。例如,对于 Guice,您有 AbstractModule ,其中 HK2 有 AbstractBinder .对于这两个组件,您将使用类似的 bind(..).to(..).in(Scope)句法。一个区别是 Guice 是 bind(Contract).to(Impl) ,而 HK2 是 bind(Impl).to(Contract) .

HK2 还有 Factory s,它允许更复杂地创建可注入(inject)对象。对于您的工厂,您将使用语法 bindFactory(YourFactory.class).to(YourContract.class) .

话虽这么说,您可以使用类似以下的内容来实现您的用例。

  1. 创建 Factory对于英语 SessionFactory

    public class EnglishSessionFactoryFactory implements Factory<SessionFactory> {
        @Override
        public SessionFactory provide() {
           ...
        }
        @Override
        public void dispose(SessionFactory t) {}
    }
    
  2. 创建 Factory对于法国人 SessionFactory

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

    注意上面两个SessionFactory s 将在单例范围内按名称绑定(bind)。

  3. 创建另一个 Factory这将在请求范围内,将使用请求上下文信息。本厂将注入(inject)以上两种SessionFactory s 按名称(使用名称绑定(bind)),并根据任何请求上下文信息,返回适当的 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. 然后你可以注入(inject) SessionFactory (我们会给它一个不同的名字)到你的 dao 中。

    public class IDaoImpl implements IDao {
    
        private final SessionFactory sessionFactory;
    
        @Inject
        public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) {
            this.sessionFactory = sessionFactory;
        }
    }
    
  5. 要将所有内容绑定(bind)在一起,您将使用 AbstractBinder类似于下面的实现

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

    这里有一些关于 Binder 的注意事项

    • 两种不同的语言具体SessionFactory s 由名称绑定(bind)。用于 @Named注入(inject),如您在第 3 步中所见。
    • 做出决定的请求范围工厂也被赋予一个名称。
    • 您会注意到 proxy(true).proxyForSameScope(false) .这是必需的,因为我们假设 IDao将是一个单例,并且由于“选择”SessionFactory我们在请求范围内,我们无法注入(inject)实际的 SessionFactory ,因为它会随着请求的不同而变化,所以我们需要注入(inject)一个代理。如果IDao是请求范围的,而不是单例,那么我们可以省略这两行。将 dao 请求设置为范围可能会更好,但我只是想展示它应该如何作为一个单例来完成。

      另见 Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey ,以获取有关此主题的更多检查。

  6. 然后你只需要注册 AbstractBinder与 Jersey 。为此,您可以使用 register(...) ResourceConfig 的方法. See also ,如果您需要 web.xml 配置。

就是这样。下面是使用 Jersey Test Framework 的完整测试.您可以像运行任何其他 JUnit 测试一样运行它。 SessionFactory使用的只是一个虚拟类,而不是实际的 Hibernate 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));
    }
}

您可能还需要考虑的另一件事是关闭 SessionFactory秒。虽然 Factory有一个 dispose()方法,它不能被 Jersey 可靠地调用。您可能想查看 ApplicationEventListener .您可以注入(inject) SessionFactory进入它,并在关闭事件中关闭它们。

关于java - 基于带有 Jersey 的服务器主机的 Hibernate 持久性上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35189278/

相关文章:

Java 对象引用冗余

java - 集合的自定义 Hibernate 插入,@WhereJoinTable 的对应物

java - 寻找最长公共(public)子串java的高效循环程序

java - Jersey + Spring 独立 web 服务 @Autowire 不工作

java - Java 作业。找出连续五位数字的最大乘积

java - 有没有办法从 StringBuilder 解析日期?

java - 连接表上的 HIbernate @manytomany 标准

java - 如何拦截 Android 耳机上的按钮按下?

java - 当你得到 "Activity is not responding, Force Close, Wait"时,从哪里开始调试?

java - 保持 Jersey Client API 函数和 REST (Jersey API) Server 函数链接的 "proper"和正确方法是什么?