1

I have a jax-rs/jersey rest application that uses Hibernate 5.2 as the orm. There is also a spring filter that handles the authentication using tokens. Everything works pretty well but there is a small problem. Each dao object is creating its own session factory on construction like so.

public abstract class BaseDAO<T> {

protected SessionFactory sessionFactory = getSessionFactory();
protected final Validator validator = getValidator();

protected SessionFactory getSessionFactory() {
    try {
        return (SessionFactory) new Configuration().configure().buildSessionFactory();
    } catch (Exception e) {
        throw new IllegalStateException("Could not locate SessionFactory in JNDI");
    }
}

protected Validator getValidator() {
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    return factory.getValidator();
}

@SuppressWarnings({"hiding", "unchecked"})  
public <T> T save(final T o) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    T savedObj = (T) session.save(o);
    tx.commit();    
    session.close();        
    return savedObj;
}

public void delete(final Object object) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    session.delete(object);
    tx.commit();        
    session.close();        
}

@SuppressWarnings("hiding")     
public <T> T get(final Class<T> type, final int id) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.beginTransaction();    
    T obj = session.get(type, id);
    tx.commit();    
    session.close();
    return obj;
}

@SuppressWarnings({"hiding", "unchecked"})
public <T> T merge(final T o) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    T obj = (T) session.merge(o);
    tx.commit();    
    session.close();        
    return obj;
}

@SuppressWarnings("hiding") 
public <T> void saveOrUpdate(final T o) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    session.saveOrUpdate(o);
    tx.commit();    
    session.close();        
}

@SuppressWarnings({ "hiding", "deprecation", "unchecked" }) 
public <T> List<T> getAll(final Class<T> type) {
    final Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.beginTransaction();
    final Criteria crit = session.createCriteria(type);
    List<T> results = crit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).list();
    tx.commit();    
    session.close();        
    return results;
}

@SuppressWarnings({ "hiding", "deprecation", "unchecked" })
public <T> List<T> findByExample(final Class<T> type, T instance) {
    try {
        Session session = sessionFactory.getCurrentSession();
        Transaction tx = session.beginTransaction();
        List<T> results = session
                .createCriteria(type)
                .add(Example.create(instance))
                .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
                .list();
        tx.commit();
        session.close();            
        return results;
    } catch (RuntimeException re) {
        //log.error("find by example failed", re);
        throw re;
    }
}

This is a problem because if you create multiple dao objects, you quickly run out of connections and even though I call session.close() after each dao call. After making too many calls to the resources, my tiny db instance is complaining about too many connections. My immediate thoughts were that the session factory needed to be managed by jersey. So I attempted to a factory binder like so:

public class HibernateSessionFactory implements Factory<SessionFactory> {

    protected SessionFactory sessionFactory;

    @Override
    public void dispose(SessionFactory arg0) {
        sessionFactory.close();
    }

    @Override
    public SessionFactory provide() {
        try {
            sessionFactory = (SessionFactory) new Configuration().configure().buildSessionFactory();
            return sessionFactory;
        } catch (Exception e) {
            throw new IllegalStateException("Could not locate SessionFactory     in JNDI");
        }       
    }
}

In the resource config:

    register(new AbstractBinder() {
        @Override
        protected void configure() {
            bindFactory(HibernateSessionFactory.class).to(SessionFactory.class);
        }
    });

And changing BaseDAO to this

@Inject
protected SessionFactory sessionFactory 

It doesn't seem to work - session factory is always null. The other problem is that the spring filter can't use the jersey inject. The filter is configured like this.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Order(2)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    private final com.renewedvoices.security.UserService userService;
    private final TokenAuthenticationService tokenAuthenticationService;
    private final SessionFactory sessionFactory;

    public SpringSecurityConfig() {
        super(true);
        this.userService = new UserService();
        this.tokenAuthenticationService = new     TokenAuthenticationService("tooManySecrets", userService);
        this.sessionFactory = createSessionFactory();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            // Custom Token based authentication based on the header previously given to the client     
            http
                .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
                    UsernamePasswordAuthenticationFilter.class)         
                .authorizeRequests()
                .antMatchers("/rest/auth/**").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling().and()
                .anonymous().and()
                .servletApi().and()
                .headers().cacheControl();

    }

    private SessionFactory createSessionFactory() {
        try {
            return (SessionFactory) new org.hibernate.cfg.Configuration().configure().buildSessionFactory();
        } catch (Exception e) {
            throw new IllegalStateException("Could not locate SessionFactory in JNDI");
        }               
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService()).passwordEncoder(new     BCryptPasswordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    @Override
    public UserService userDetailsService() {
        return userService;
    }

    @Bean
    public TokenAuthenticationService tokenAuthenticationService() {
       return tokenAuthenticationService;
    }

    @Bean
    public SerssionFactory sessionFactory() {
       return sessionFactory;
    }
}

Whether I use @Autowired or @Inject in BaseDao, sessionFactory is always null. I cannot switch over to purely spring. Is there any way to get this sessionFactory working? Is this the best way to handle the session factory in a rest service or is there a better way to not open a new session on each request? Almost all of the answers I have found have been spring-only solutions. Any help is greatly appreciated.

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
user3170736
  • 511
  • 5
  • 24
  • You need to make the DAO a Spring bean so that it can be injected into Spring components. Then for Jersey you need to integrate Jersey to use Spring components so you can inject into Jersey. Have a look at [this post](http://stackoverflow.com/a/32357991/2587435) – Paul Samsotha Dec 19 '16 at 03:45
  • I read through the answer and I looked at the linked git projects. I am not sure I follow. Can you help point out the gap between what I am doing wrong above and what they are doing? I have tried marking @@Component and configuring the bean definition as shown above. – user3170736 Dec 19 '16 at 12:37
  • I would've posted an answer, but it is not a trivial set up. If I have time later I will try to put together a sample project for you. If not today, maybe some time tomorrow – Paul Samsotha Dec 19 '16 at 12:41

1 Answers1

1

Continuing from my above comment

You need to make the DAO a Spring bean so that it can be injected into Spring components. Then for Jersey you need to integrate Jersey to use Spring components so you can inject into Jersey. Have a look at this post.

So what you need to do is make the DAO a Spring Bean. You can do so like

@Configuration
public class DataConfiguration {
    @Bean
    public MyDao myDao() {
        return new MyDao();
    }
}

Then in your AbstractSecurityWebApplicationInitializer you need to add the configuration class, and also override the contextConfigLocation property so that Jersey doesn't try to create a ContextLoaderListener. This is already created by Spring Security

@Order(1)
public class DemoSecurityWebApplicationInitializer
        extends AbstractSecurityWebApplicationInitializer {

    public DemoSecurityWebApplicationInitializer() {
        super(DataConfiguration.class, SecurityConfig.class);
    }

    @Override
    public void afterSpringSecurityFilterChain(ServletContext servletContext) {
        // Set the Jersey used property to it won't load a ContextLoaderListener
        servletContext.setInitParameter("contextConfigLocation", "NOOP");
    }
}

The the last thing you need to do is add the jersey-spring3 dependency. This is the Jersey dependency that allows it to integrate with Spring components (only Spring to Jersey, not Jersey to Spring).

<dependency>
    <groupId>org.glassfish.jersey.ext</groupId>
    <artifactId>jersey-spring3</artifactId>
    <exclusions>
       <!-- exclude Spring 3 if you want to use Spring 4 -->
    </exclusions>
<dependency>

See complete example in this GitHub project.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720