5

I have a web project working with Spring3 and Hibernate4 and now I want to test the DAOs without using the xml files. To do so I have created a class that creates a LocalSessionFactoryBean with the data contained in the application's xml file and a simple test class.

However, the sessionFactory returned by localSessionFactoryBean.getObject() is null. I have been looking at some examples like this and they have the same problem when I modify them to run without Spring. Do you have any idea?

This is the code that prepares the sessionFactory:

@Configuration
@Transactional
@EnableTransactionManagement
@ComponentScan({ "com.company" })
public class HibernateInitializator {

    public SessionFactory getSessionFactory() {

        Properties hibernateProperties = getHibernateProperties();
        DataSource dataSource = getDatasourceConfiguration();
        LocalSessionFactoryBean localSessionFactoryBean = generateSessionFactoryBean(new String[] { "com.company" },
            dataSource, hibernateProperties);
        SessionFactory sessionFactory = localSessionFactoryBean.getObject();

        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(sessionFactory);

        return sessionFactory;
    }

    private DataSource getDatasourceConfiguration() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/dbName");
        dataSource.setUsername("username");
        dataSource.setPassword("password");

        return dataSource;
    }

    private static LocalSessionFactoryBean generateSessionFactoryBean(String[] basePackage, DataSource dataSource,
        Properties hibernateProperties) {

        LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
        localSessionFactoryBean.setDataSource(dataSource);
        localSessionFactoryBean.setPackagesToScan(basePackage);
        localSessionFactoryBean.setHibernateProperties(hibernateProperties);

        return localSessionFactoryBean;
    }

    private static Properties getHibernateProperties() {

        Properties hibernateProperties = new Properties();
        hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5InnoDBDialect");
        hibernateProperties.put("hibernate.show_sql", false);
        hibernateProperties.put("hibernate.generate_statistics", false);
        hibernateProperties.put("hibernate.hbm2ddl.auto", "update");
        hibernateProperties.put("hibernate.use_sql_comments", false);

        return hibernateProperties;
    }
}

And this is a simple test class that uses it:

public class GenericDAOHibernateTest {

    private GenericDAOHibernate dao;

    @BeforeTest
    private void testInitialization() {

        dao = new GenericDAO();
        HibernateInitializator initializator = new HibernateInitializator();
        SessionFactory sessionFactory = initializator.getSessionFactory();
        dao.setSessionFactory(sessionFactory);
    }

    @Test(description = "Checks that returns the user list ")
    public void shouldReturnsUserList() throws SQLException, Exception {

        List<Object[]> openResultSetList = dao.doSomeOperation();
        ...
    }
}
rafaborrego
  • 610
  • 1
  • 8
  • 19

3 Answers3

12

Try adding this line

localSessionFactoryBean.afterPropertiesSet();

in the method after the properties of LocalSessionFactoryInstance has been set. Your method will be as

private static LocalSessionFactoryBean generateSessionFactoryBean(String[] basePackage, DataSource dataSource,
        Properties hibernateProperties) {
        LocalSessionFactoryBean localSessionFactoryBean = new LocalSessionFactoryBean();
        localSessionFactoryBean.setDataSource(dataSource);
        localSessionFactoryBean.setPackagesToScan(basePackage);
        localSessionFactoryBean.setHibernateProperties(hibernateProperties);
        // Added the below line
        localSessionFactoryBean.afterPropertiesSet();
        return localSessionFactoryBean;
    }

This link may add more insight to the problem.

As per Documentation,

public void afterPropertiesSet() throws IOException

Invoked by a BeanFactory after it has set all bean properties supplied (and satisfied BeanFactoryAware and ApplicationContextAware). This method allows the bean instance to perform initialization only possible when all bean properties have been set and to throw an exception in the event of misconfiguration.

In your case, I think you need to call it in your code manually.

Keerthivasan
  • 12,760
  • 2
  • 32
  • 53
  • Thank you, with that line the sessionFactory is initialized correctly. Now I have problems using the DAO because it launches an HibernateException (“No Session found for current thread”) although I have added annotations like @Transactional. I have been looking for a while and I haven't found how to solve it. I will comment it here as soon as I find it – rafaborrego May 27 '14 at 14:41
  • Glad, it helped. You can create a new question and ask help here. If you find the answer on your own, then post it and accept it. It will help someone in future. – Keerthivasan May 27 '14 at 14:43
  • After trying it for a while I decided to do otherwise. I posted it here as a different answer so that other people can use it in the future. Thanks for the advice – rafaborrego May 27 '14 at 22:09
  • For the same i have given hibernate cfg to config location. But getting following error Caused by: org.hibernate.service.UnknownUnwrapTypeException: Cannot unwrap to requested type [javax.sql.DataSource] at org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl.unwrap(DriverManagerConnectionProviderImpl.java:222) – Shashi Dk Jul 15 '16 at 02:51
3

I asked how to do it without an xml file and the solution proposed by @Octopus fixed the problem I had. However, after fixing that arised other errors with transactions... so I decided to do it in a different way that doesn't interfere with the application.

To do so I created a simplified version of the xml file in /src/test/resources removing many configurations like c3p0's one and hardcoding the values so that the tests won't fail if the application's properties files are changed.

This is the content of the xml file:

<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans" ...>

    <!-- Spring DataSource -->
    <bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/testDatabase" />
        <property name="username" value="testUsername" />
        <property name="password" value="testPassword" />
    </bean>

    <!-- Hibernate 4 SessionFactory -->
    <bean id="testSessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" p:dataSource-ref="testDataSource">
        <property name="packagesToScan" value="com.company" />
        <property name="hibernateProperties">
            <props>
                <!-- Hibernate basic configuration -->
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.generate_statistics">false</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
                <prop key="hibernate.use_sql_comments">false</prop>
            </props>
        </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="dataSource" ref="testDataSource" />
        <property name="sessionFactory" ref="testSessionFactory" />
    </bean>

    <context:annotation-config />
    <tx:annotation-driven />

    <!-- Initialization of DAOs -->
    <bean id="userDao" name="userDao" class="com.company.dao.UserDAOHibernate" autowire="byName"/>
    ... 
</beans>

In the test class I have used JUnit because I haven't managed to do it with TestNG although I tried extending AbstractTestNGSpringContextTests. I would recommend using JUnit 4.4 version instead of newer versions because it has classes like AssumptionViolatedException that are used by spring-test 2.5.

This is the code:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/test/resources/application-config-tests.xml" })
public class SimpleUserDAOTest {

    @Autowired
    private UserDAO dao;

    @Autowired
    private SessionFactory sessionFactory;

    @Before
    public void setUp() throws Exception {
        TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(sessionFactory.openSession()));
    }

    @After
    public void tearDown() throws Exception {
        SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
        SessionFactoryUtils.closeSession(sessionHolder.getSession());
    }

    @Test
    public void shouldReturnActiveUsers() {
        List<User> userList = dao.getActiveUsers();
        ...
    }
}

I hope this helps you in your future developments

rafaborrego
  • 610
  • 1
  • 8
  • 19
0

When you are using Spring 3+ and Hibernate 4, I would suggest you a better way, define everything as annotated Spring Configurations and this work smoothly.

Below is the detailed code, just copy and paste in your project and you can Autowire SessionFactory anywhere in the DAO layer or any spring component :)

@Configuration
@Import(HibernateConfig.class)
@EnableWebMvc
@ComponentScan(basePackages = "com.mypackages")
public class WebConfig extends WebMvcConfigurationSupport {

@Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.parameterName("mediaType").ignoreAcceptHeader(true)defaultContentType(MediaType.APPLICATION_JSON).mediaType("xml", MediaType.APPLICATION_XML).mediaType("json", MediaType.APPLICATION_JSON);
}

@Bean(name = "validator")
public Validator validator() {
    return new LocalValidatorFactoryBean();
}
} 

Then define a HibernateConfig as below:

@Configuration
@EnableTransactionManagement
@EnableAspectJAutoProxy
@PropertySource({"classpath:app.properties"})
@ComponentScan(basePackages = "com.mypackages")
public class HibernateConfig {

@Value(Constants.HIBERNATE_DIALECT)
private String hibernateDialect;

@Autowired
private DataSource dataSource;

@Bean(name = "appProperty")
public static PropertySourcesPlaceholderConfigurer appProperty() {
    return new PropertySourcesPlaceholderConfigurer();
}

@Bean(name = "sessionFactory")
public SessionFactory getSessionFactory() throws Exception {

    Properties properties = new Properties();
    properties.put(Constants.HIBERNATE_DIALECT_PROPERTY,
            hibernateDialect);
    properties.put(Constants.HIBERNATE_SHOW_SQL_PROPERTY,
            "false");
    properties
            .put(Constants.HIBERNATE_CURRENT_SESSION_CONTEXT_CLASS_PROPERTY,
                    "thread");
    properties
    .put("dynamic-update","true");
    LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
    factory.setPackagesToScan(new String[] { Constants.DOMAIN_MODEL_PACKAGE });
    factory.setDataSource(dataSource);
    factory.setHibernateProperties(properties);
    factory.afterPropertiesSet();
    return factory.getObject();
}

@Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager() throws Exception {
    return new HibernateTransactionManager(getSessionFactory());
}
}

Then define a JndiConfig as below:

@Configuration
public class JndiConfig {

@Value(Constants.DRIVER_CLASS)
private String driverClassName;
@Value(Constants.DATABASE_URL)
private String databaseURL;
@Value(Constants.USER_NAME)
private String username;
@Value(Constants.PASSWORD)
private String password;
@Value(Constants.MAX_ACTIVE)
private int maxActive;
@Value(Constants.MAX_IDLE)
private int maxIdle;
@Value(Constants.MAX_WAIT)
private long maxWait;
@Value(Constants.MIN_IDLE)
private int minIdle;
@Value(Constants.INITIAL_SIZE)
private int initialSize;
@Value(Constants.TIME_BETWEEN_EVICTION)
private long timeBetweenEvictionRunsMillis;

@Bean(name = "dataSource")
public DataSource dataSource() throws Exception {
    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName(driverClassName);
    dataSource.setUrl(databaseURL);
    dataSource.setUsername(username);
    dataSource.setPassword(password);
    dataSource.setTestOnBorrow(true);
    dataSource.setTestWhileIdle(true);
    dataSource.setValidationQuery("SELECT 1");
    dataSource.setMaxActive(maxActive);
    dataSource.setMaxIdle(maxIdle);
    dataSource.setMaxWait(maxWait);
    dataSource.setMinIdle(minIdle);
    dataSource
            .setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

    return dataSource;
}
}
dharam
  • 7,882
  • 15
  • 65
  • 93
  • Thanks. However, I have been trying this code and it doesn't work for me, not even launches the Hibernate configuration. Maybe I am not injecting the dependency correctly or it is not possible to inject it in a test class. I have also tried creating JndiConfig object and injecting the datasource to an HibernateConfig object, and it doesn't work that way – rafaborrego May 27 '14 at 15:15
  • did you put all these classes in com.mypackages. If yes then please post the error. – dharam May 27 '14 at 16:10
  • I put them in other package and changed the value of basePackages. There wasn't any error displayed on the console, but sessionFactory was null. Don't worry, I have solved it in a different way that I have commented in other answer. Thanks anyway – rafaborrego May 27 '14 at 22:32