使用HK2向Jersey返回hibernate会话

时间:2015-08-07 23:46:20

标签: java hibernate dependency-injection jersey-2.0 hk2

我正在开发小应用程序并且在DI方面存在一些问题。

我有一个存储库类用于保存我注入到我的服务中的实体。我想使用H2K向它注入Session对象。 为此,我尝试执行以下SO帖子中描述的类似内容:

  1. Jersey + HK2 + Grizzly: Proper way to inject EntityManager?
  2. Using Jersey 2.0, how do you register a bindable instance per request?
  3. How do I properly configure an EntityManager in a jersey / hk2 application?
  4. 所以我创建了SFFactory类并在ApplicationConfig中注册它。

    SFFactory.java

    import org.glassfish.hk2.api.Factory;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
    import org.hibernate.cfg.Configuration;
    
    public class SFFactory implements Factory<Session> {
    
    private SessionFactory factory;
    
    public SFFactory() {
        Configuration configuration = new Configuration();
        configuration.configure("hibernate.cfg.xml");
        StandardServiceRegistryBuilder srBuilder = new StandardServiceRegistryBuilder();
        srBuilder.applySettings(configuration.getProperties());
        factory = configuration.buildSessionFactory(srBuilder.build());
        }
    
    @Override
    public Session provide() {
        return factory.openSession();
        }
    
    @Override
    public void dispose(Session session) {
        if (session.isOpen()) {
            session.close();
            }
        }
    }
    

    ApplicationConfig.java

    import org.alexdzot.phonettesttask.repository.MessageRepository;
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.hibernate.Session;
    
    import javax.inject.Singleton;
    import javax.ws.rs.ApplicationPath;
    
    @ApplicationPath("/rest/*")
    public class ApplicationConfig extends ResourceConfig {
    
    public ApplicationConfig() {
        packages("org.alexdzot.phonettesttask");
        register(new AbstractBinder() {
            @Override
            protected void configure() {
                bindFactory(SFFactory.class).to(Session.class);
                bindFactory(MessageRepositoryFactory.class).to(MessageRepository.class).in(Singleton.class);
                }
            });
        }
    }
    

    DefaultMessageRepository

    import org.alexdzot.phonettesttask.model.Message;
    import org.hibernate.Session;
    import org.hibernate.Transaction;
    import org.jvnet.hk2.annotations.Service;
    
    import javax.inject.Inject;
    import java.util.List;
    
    @Service
    public class DefaultMessageRepository implements MessageRepository {
    
    @Inject
    private Session session;
    
    public void saveMessage(Message message) {
        Session session = null;
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
            session.save(message);
            tx.commit();
            } catch (Exception e) {
            tx.rollback();
            } finally {
            session.close();
            }
        }
    
    public List<Message> getAllMessages() {
        Session session = null;
        Transaction tx = null;
        List<Message> messages = null;
        try {
            tx = session.beginTransaction();
            messages = session.createCriteria(Message.class).list();
    
            } catch (Exception e) {
            tx.rollback();
            } finally {
            session.close();
            return messages;
            }
        }
    }
    

    但是当我运行应用程序并尝试调用存储库方法时,我得到了NullPoinerException。我无法理解我做错了什么。 这就是Tomcat日志所说的:

    *08-Aug-2015 02:42:18.232 SEVERE [http-nio-8080-exec-10] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [org.alexdzot.phonettesttask.config.ApplicationConfig] in context with path [] threw exception [java.lang.NullPointerException] with root cause
     java.lang.NullPointerException
        at org.alexdzot.phonettesttask.repository.DefaultMessageRepository.getAllMessages(DefaultMessageRepository.java:42)
        at org.alexdzot.phonettesttask.service.MessageResource.viewSentMessages(MessageResource.java:38)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:497)
        at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory$1.invoke(ResourceMethodInvocationHandlerFactory.java:81)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:144)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:161)
        at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:205)
        at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:99)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:389)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:347)
        at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:102)
        at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:308)
        at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
        at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
        at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
        at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
        at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:291)
        at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1140)
        at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:403)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:386)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:334)
        at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
        at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:617)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1521)
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1478)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
        at java.lang.Thread.run(Thread.java:745)*
    

    任何想法我的解决方案有什么问题以及导致该问题的原因是什么?

1 个答案:

答案 0 :(得分:5)

主要问题:

  

[java.lang.NullPointerException]

查看您的存储库类

@Service
public class DefaultMessageRepository implements MessageRepository {

    @Inject
    private Session session; <-------------------+
                                                 | // You shadow session
    public void saveMessage(Message message) {   |
        Session session = null;   <--------------+
        Transaction tx = null;
        try {
            tx = session.beginTransaction();
        ...
    }
    ...
}

您正在注入注入的字段session。所以你只是使用本地session,它是null。所以在你的方法中,只需摆脱Session session = null;。注入应该可以正常工作,您应该只能使用session字段。

另一个问题:

所有请求都使用相同的Session。由于DefaultMessagFactory是单身,HK2声称Session也应该是单身,因为它包含在单身中。因此,不是将SFFactory作为默认@PerLookup范围,而是获得一个单例工厂,因此只会创建一个Session。这不是您应该要的行为。您可以通过在工厂类中放置一些打印语句来测试它。

确保为每个请求创建新Session的一种方法是:

  1. 使用javax.inject.Provider<Session>,懒洋洋地加载会话,这将允许使用它来保持我们配置的范围。

    @Inject
    private javax.inject.Provider<Session> session;
    
    @Override
    public void saveEvent(Event event) {
        Session s = session.get();
        Transaction tx = s.beginTransaction(); 
    
  2. 在请求范围中配置SFFactory

    @Override
    protected void configure() {
        bindFactory(SFFactory.class)
                .to(Session.class)
                .in(RequestScoped.class);
    }
    
  3. 要解决的另一件事是SessionFactory应该是应用程序中的单个实例。为此,我们可以为它创建一个工厂,并将其注入SFFactory。例如

    public class SessionFactoryFactory implements Factory<SessionFactory> {
    
        private final SessionFactory factory;
    
        public SessionFactoryFactory() {
            Configuration configuration = new Configuration();
            configuration.configure("hibernate.cfg.xml");
            StandardServiceRegistryBuilder srBuilder = new StandardServiceRegistryBuilder();
            srBuilder.applySettings(configuration.getProperties());
            factory = configuration.buildSessionFactory(srBuilder.build());
            System.out.println("--- SessionFactory Created ---");
        }
    
        @Override
        public SessionFactory provide() {
            System.out.println("--- SessionFactory Provided ---");
            return factory;
        }
    
        @Override
        public void dispose(SessionFactory factory) {
            factory.close();
        }
    }
    

    SFFactory

    public class SFFactory implements Factory<Session> {
    
        private final SessionFactory factory;
    
        @Inject
        public SFFactory(SessionFactory factory) {
            this.factory = factory;
        }
    
        @Override
        public Session provide() {
            System.out.println("--- Session Created ---");
            return factory.openSession();
        }
    
        @Override
        public void dispose(Session session) {
            if (session.isOpen()) {
                session.close();
            }
        }
    }
    

    您的配置会更改为

    @Override
    protected void configure() {
        bindFactory(SessionFactoryFactory.class)
                .to(SessionFactory.class)
                .in(Singleton.class);
        bindFactory(SFFactory.class)
                .to(Session.class)
                .in(RequestScoped.class);
    }
    

    更新

    所以除了阴影之外,另一个主要问题是OP没有显示的MessageRepositoryFactory(在评论的github项目中)。

    public class MessageRepositoryFactory implements Factory<MessageRepository> {
        @Override
        public MessageRepository provider() {
            return new DefaultMessageRepository();
        }
        ...
    }
    

    当您实例化自己的对象时,默认情况下,框架不会处理注入。这就是为什么注射Session没有发生的原因。

    您可以像这样绑定

    ,而不是像这样使用Factory
    bind(DefaultMessageRepository.class).to(MessageRepository.class).in(..);
    

    这样框架就会创建实例。 Factory实际上只应该在无法绑定上述方式的地方使用,例如你有一些初始化要做。我们甚至可能没有为Factory创建SessionFactoryFactory,而是在其他地方完成了所有会话工厂配置,只是绑定了实例。例如

    SessionFactory sessionFactory =...
    ...
    bind(sessionFactory).to(SessionFactory.class);
    

    我们只绑定一个SessionFactory单例。