3

I'm writing a DAL with Spring Data and Hibernate but I'm running into a IllegalArgumentException exception which is stopping my work.

Here is the DALConf.java class which contains DataSource and persistence exception translation processor configurations

package my.dal.config;

import java.util.Properties;

import javax.annotation.Resource;
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;


@Configuration
@ComponentScan(basePackages = { "my.dal" })
@PropertySource("classpath:dbconnection.properties")
public class DALConfig {

    private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";  
    private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";  
    private static final String PROPERTY_NAME_DATABASE_URL = "db.url";  
    private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";  
    private static final String PROPERTY_NAME_POOL_INITIAL_SIZE = "pool.initialsize";
    private static final String PROPERTY_NAME_POOL_MAX_IDLE = "pool.maxidle";

    @Resource
    private Environment environment;   

    @Bean
    public DataSource dataSource() throws Exception
    {
        Properties props = new Properties();

        props.put("driverClassName", environment.getProperty(PROPERTY_NAME_DATABASE_DRIVER));
        props.put("url", environment.getProperty(PROPERTY_NAME_DATABASE_URL));
        props.put("username", environment.getProperty(PROPERTY_NAME_DATABASE_USERNAME));
        props.put("password", environment.getProperty(PROPERTY_NAME_DATABASE_PASSWORD));
        props.put("initialSize", environment.getProperty(PROPERTY_NAME_POOL_INITIAL_SIZE));
        props.put("maxIdle", environment.getProperty(PROPERTY_NAME_POOL_MAX_IDLE));

        BasicDataSource bds = BasicDataSourceFactory.createDataSource(props);

        return bds; 
    }

    @Bean
    public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor()
    {
        PersistenceExceptionTranslationPostProcessor b = new PersistenceExceptionTranslationPostProcessor();
        return b;
    }   
}

Then here is the HibernateConfig.class which contains Hibernate configurations

package my.dal.config;

import java.util.Properties;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = { "my.dal" })
@PropertySource("classpath:hibernate.properties")
@EnableTransactionManagement
public class HibernateConfig {

    private static final String PROPERTY_NAME_DAL_CLASSES_PACKAGE = "hibernate.dal.package";  
    private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.showsql";

    @Resource
    private Environment environment;  

    @Autowired
    DataSource dataSource;

    @Bean
    public SessionFactory sessionFactory()
    {

        LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();

        lsfb.setPackagesToScan(environment.getProperty(PROPERTY_NAME_DAL_CLASSES_PACKAGE));

        Properties hibernateProperties = new Properties();
        hibernateProperties.put("dialect", environment.getProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
        hibernateProperties.put("show_sql", environment.getProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));

        lsfb.setHibernateProperties(hibernateProperties);
        lsfb.setDataSource(dataSource);
        return lsfb.getObject();

    }

    @Bean 
    public HibernateExceptionTranslator hibernateExceptionTranslator(){ 
      return new HibernateExceptionTranslator(); 
    }


    @Bean
    public HibernateTransactionManager transactionManager()
    {
        // HERE THE EXCEPTION IS THROWN
        HibernateTransactionManager htm = new HibernateTransactionManager(sessionFactory());
        return htm;
    }   

}

This is the DAO UserDAO.java

package my.dal.dao;

import my.models.dal.User;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;


@Repository
public class UserDAO
{

    private SessionFactory sessionFactory;

    @Autowired
    public UserDAO(SessionFactory sessionFactory) {
        this.sessionFactory=sessionFactory;
    }

    @Transactional
    public int insert(User user) {
        return (Integer) sessionFactory.getCurrentSession().save(user);
    }

    @Transactional
    public User getByUsername(String username) {
        return (User) sessionFactory.getCurrentSession().get(User.class, username);
    }

    @Transactional
    public void update(User user) {
        sessionFactory.getCurrentSession().merge(user);
    }

    @Transactional
    public void delete(String username) {
        User u = getByUsername(username);
        sessionFactory.getCurrentSession().delete(u);
    }

}

Finally, this is the test class DALTest.java

package my.dal.tests;

import static org.junit.Assert.assertTrue;
import my.dal.config.DALConfig;
import my.dal.config.HibernateConfig;
import my.dal.dao.UserDAO;
import my.models.dal.User;
import org.hibernate.SessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@ContextConfiguration(classes = { DALConfig.class, HibernateConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class DALTest {

    @Autowired
    SessionFactory sessionFactory;

    @Test
    public void testGetUser() {
        UserDAO userDAO = new UserDAO(sessionFactory);
        User user = null;
        user = userDAO.getByUsername("mrossi");

        assertTrue(null != user);
    }

}

The execution of the test ends with the following exception

...
Caused by: java.lang.IllegalArgumentException: Property 'sessionFactory' is required
    at org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:247)
    at org.springframework.orm.hibernate4.HibernateTransactionManager.<init>(HibernateTransactionManager.java:130)
    at my.dal.config.HibernateConfig.transactionManager(HibernateConfig.java:66)
...

It is thrown at this line

HibernateTransactionManager htm = new HibernateTransactionManager(sessionFactory());

It seems like Spring cannot instantiate the sessionFactory bean but I don't know what could be the problem...

What do you think about that?

Thank you

Ashot Karakhanyan
  • 2,804
  • 3
  • 23
  • 28
gvdm
  • 3,006
  • 5
  • 35
  • 73

3 Answers3

7

You forgot to call

lsfb.afterPropertiesSet()

before getting the object from lsfb. afterPropertiesSet() is the method that builds and exposes the session factory.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Thank you :) Now the problem is that at this line of code (where I actually use the session) user = userDAO.getByUsername("mrossi"); I have another exception org.hibernate.HibernateException: No Session found for current thread at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97) at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:941) at it.prisma.dal.dao.UserDAO.getByUsername(UserDAO.java:36) Debug shows me that the session factory is properly built. What am I doing wrong? – gvdm Apr 09 '14 at 14:10
  • Solved following sendon's answer – gvdm Apr 09 '14 at 15:17
1

One way to fix that is that you are using Constructor Injection for the sessionFactory which is not working with annotation exposed bean well. (Not sure how Spring 4 makes any improvement on that. I only used Spring 3.5 and below)

I would recommend to use getter/setter method injection for that in UserDAO.java

private SessionFactory  sessionFactory;

@Autowired
public void setSessionFactory(SessionFactory sessionFactory)
{
    this.sessionFactory = sessionFactory;
}

So annotation can get the bean.

sendon1982
  • 9,982
  • 61
  • 44
  • Hi sendon Thank you. I've edited the code following your advice but now I get a NullPoinetrException at the line user = userDAO.getByUsername("mrossi"); The strange think is that debug shows me that the sessionFactory is correctly instantiated in the setter method – gvdm Apr 09 '14 at 14:20
  • When I ran the code, I got an exception: org.hibernate.service.UnknownUnwrapTypeException: Cannot unwrap to requested type [javax.sql.DataSource] Do you have this issue? – sendon1982 Apr 10 '14 at 00:00
  • Strange, I don't get that Exception (but another one concertning Hibernate'Entity). Keep in mind that I omitted in this post the entity class and the properties files. Perhaps your exception is related to these lack of information? – gvdm Apr 10 '14 at 07:43
  • Maybe that is the case. But usually when we use spring + hibernate, we should let spring to handle these DB connection, datasource, not hibernate – sendon1982 Apr 10 '14 at 13:02
1

As JB Nizet suggested, you're missing the call to afterPropertiesSet()which is needed if you handle the object lifecycle yourself. I'd like to propose a slightly better version of your config to avoid this issue, which you might run into in other cases as well.

Whenever you configure a FactoryBean in JavaConfig, return the factory and refer to the actual object to be produced on the client methods. In your example this would look something like this:

@Configuration
class YourConfig {

  @Bean
  public LocalSessionFactoryBean sessionFactory() {
    // setup factory
  }

  @Bean
  public HibernateTransactionManager transactionManager(SessionFactory factory) {
    return new HibernateTransactionManager(factory);
  }
}

As you can see, we don't manually invoke any lifecycle callbacks on the factory as we have to return it as is. Spring will do that for us. It will also invoke ….getObject() on it to create the actual SessionFactory and hand it into the @Bean method to create the transaction manager.

Oliver Drotbohm
  • 80,157
  • 18
  • 225
  • 211
  • Thank you for the response. Should I @Autowire the transactionManager bean so that the transactionManager's factory parameter is instantiated according to the sessionFactory() bean? – gvdm Apr 10 '14 at 07:31
  • Well no. The transaction manager is usually detected by bean name and only used for the infrastructure. Simply using `@Bean` method parameters will cause Spring to try to resolve a bean of the parameters type. – Oliver Drotbohm Apr 10 '14 at 09:36