Search code examples
javahibernatedependency-injectionjersey-2.0hk2

Injecting hibernate session to Jersey using HK2


I'm developing little app and have some issues with DI.

I have a repository class for persisting my entities which I inject to my service. And I'd like to inject Session object to it using H2K. For this purpose I try do something similar described in following SO posts:

  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?

So I created SFFactory class and register it in 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;
        }
    }
}

But when I run the app and trying to call repository methods I get NullPoinerException. I can't understand what am I doing wrong. This is what Tomcat log is saying:

*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)*

Any ideas what wrong with my solution and what causes that problem?


Solution

  • Main problem:

    [java.lang.NullPointerException]

    Look at your repository class

    @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();
            ...
        }
        ...
    }
    

    You are shadowing the injected field session. So you are just using the local session, which is null. So in your method, just get rid of Session session = null;. The injection should work fine, and you should be able to just use the session field.

    Another Problem:

    The same Session is being used for all request. Since DefaultMessagFactory is a singleton, HK2 asserts that Session should also be a singleton, since it is contained within a singleton. So instead of making the SFFactory the default @PerLookup scope, you get a factory that is a singleton, and so only one Session will be created. This is not the behavior you should want. You can test this by putting some print statements inside your factory class.

    One way to make sure that a new Session is created for each request is to:

    1. Use javax.inject.Provider<Session>, to lazily load the session, which will allow use to keep it in the scope we configured.

      @Inject
      private javax.inject.Provider<Session> session;
      
      @Override
      public void saveEvent(Event event) {
          Session s = session.get();
          Transaction tx = s.beginTransaction(); 
      
    2. Configure the SFFactory in a request scope

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

    The other thing to fix is that SessionFactory should be a single instance through the application. For that we can create a single factory for it, and inject it into the SFFactory. For example

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

    The in your 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();
            }
        }
    }
    

    You configuration would change to look like

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

    UPDATE

    So it seems aside from the shadowing, another main problem was in the MessageRepositoryFactory that the OP didn't show (in github project from comments).

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

    When you instantiate the object your self, by default, the framework doesn't handle the injection. So that's why the injection of the Session didn't happen.

    Instead of using the a Factory like this, you could simply bind like this

    bind(DefaultMessageRepository.class).to(MessageRepository.class).in(..);
    

    This way the framework creates the instance. The Factory should really only be used where it is not possible to bind the above way, for instance you have some initializations to do. We could have even not created the Factory for the SessionFactoryFactory and instead did all the session factory configuration elsewhere and just binded the instance. For example

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

    We just bind a SessionFactory singleton.