【问题标题】:Hibernate persistence context based on server host with Jersey基于带有 Jersey 的服务器主机的休眠持久性上下文
【发布时间】:2016-02-03 22:47:32
【问题描述】:

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

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

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

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

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

有什么优雅的方法可以实现这一点吗?

【问题讨论】:

  • 您使用的是哪个 Jersey 版本?
  • 我们使用 Jersey 2.19,不时更新未成年人

标签: java hibernate jersey guice jersey-2.0


【解决方案1】:

我不是 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 还有Factorys,允许更复杂地创建您的可注入对象。对于您的工厂,您将使用语法bindFactory(YourFactory.class).to(YourContract.class)

话虽如此,您可以使用以下内容来实现您的用例。

  1. 为英文SessionFactory创建一个Factory

    public class EnglishSessionFactoryFactory implements Factory<SessionFactory> {
        @Override
        public SessionFactory provide() {
           ...
        }
        @Override
        public void dispose(SessionFactory t) {}
    }
    
  2. 为法语SessionFactory创建一个Factory

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

    请注意,上面的两个SessionFactorys 将在单例范围内按名称绑定。

  3. 在请求范围内创建另一个Factory,它将使用请求上下文信息。该工厂将通过名称注入上述两个SessionFactorys(使用名称绑定),并从任何请求上下文信息中返回适当的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. 然后,您可以将SessionFactory(我们将为其命名)注入您的 dao。

    public class IDaoImpl implements IDao {
    
        private final SessionFactory sessionFactory;
    
        @Inject
        public IDaoImpl(@Named("SessionFactory") SessionFactory sessionFactory) {
            this.sessionFactory = sessionFactory;
        }
    }
    
  5. 要将所有内容绑定在一起,您将使用类似于以下实现的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);
        }
    }
    

    这里有一些关于活页夹的注意事项

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

      另请参阅Injecting Request Scoped Objects into Singleton Scoped Object with HK2 and Jersey,了解有关此主题的更多检查。

  6. 那么你只需要在 Jersey 注册AbstractBinder。为此,您可以只使用ResourceConfigregister(...) 方法。 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));
    }
}

您可能还需要考虑的另一件事是关闭SessionFactorys。尽管Factory 有一个dispose() 方法,但它并不能被Jersey 可靠地调用。您可能想查看ApplicationEventListener。您可以将SessionFactorys 注入其中,并在关闭事件时将其关闭。

【讨论】:

  • 太棒了!特别是指出proxy(true).proxyForSameScope(false)
  • @wondra 另见stackoverflow.com/q/35994965/2587435。有了这个,您不需要为主 sessionfactory 使用额外的名称。您可以在没有名称的情况下注入它。刚学到新东西:-)
猜你喜欢
  • 1970-01-01
  • 2010-10-29
  • 1970-01-01
  • 2021-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-07
  • 1970-01-01
相关资源
最近更新 更多