简体   繁体   中英

Jersey + Hibernate Injecting SessionFactory to DAO

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.

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM