Thursday, February 17, 2011

Spring+Hibernate Application with zero XML

Spring framework came up with Annotation support since 2.5 version which eases the development.
Whether Annotation based approach better or XML approach is better is depends on the project and their personal preference.

Let us see how we can write a Simple Application using Spring and Hibernate using Annotations, no xml at all.

The configuration for JDBC datasource and Hibernate properties:

application.properties
################### JDBC Configuration ##########################
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:file:db/SivaLabsDB;shutdown=true
jdbc.username=sa
jdbc.password=

################### Hibernate Configuration ##########################
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update
hibernate.generate_statistics=true
We can instantiate ApplicationContext from a java file having @Configuration annotation.

AppConfig.java
package com.sivalabs.springmvc.config;

import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.ClassPathResource;

/**
 * @author SivaLabs
 *
 */
@Import({RepositoryConfig.class})
@Configuration
public class AppConfig
{
    //
    @Bean
    public PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer()
    {
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        ppc.setLocation(new ClassPathResource("application.properties"));
        ppc.setIgnoreUnresolvablePlaceholders(true);
        return ppc;
    }
}
Here @Import({RepositoryConfig.class}) means xml version of <import resource="applicationContext-dao.xml"></import>
package com.sivalabs.springmvc.config;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
import org.springframework.orm.hibernate3.support.IdTransferringMergeEventListener;
import org.springframework.transaction.annotation.AnnotationTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;

/**
 * @author SivaLabs
 *
 */
@Configuration
public class RepositoryConfig
{
    //${jdbc.driverClassName}
    @Value("${jdbc.driverClassName}")     private String driverClassName;
    @Value("${jdbc.url}")                 private String url;
    @Value("${jdbc.username}")             private String username;
    @Value("${jdbc.password}")             private String password;
    
    @Value("${hibernate.dialect}")         private String hibernateDialect;
    @Value("${hibernate.show_sql}")     private String hibernateShowSql;
    @Value("${hibernate.hbm2ddl.auto}") private String hibernateHbm2ddlAuto;
        
    @Bean()    
    public DataSource getDataSource()
    {
        DriverManagerDataSource ds = new DriverManagerDataSource();        
        ds.setDriverClassName(driverClassName);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);        
        return ds;
    }
    
    @Bean
    @Autowired 
    public HibernateTransactionManager transactionManager(SessionFactory sessionFactory)
    {
        HibernateTransactionManager htm = new HibernateTransactionManager();
        htm.setSessionFactory(sessionFactory);
        return htm;
    }
    
    @Bean
    @Autowired
    public HibernateTemplate getHibernateTemplate(SessionFactory sessionFactory)
    {
        HibernateTemplate hibernateTemplate = new HibernateTemplate(sessionFactory);
        return hibernateTemplate;
    }
        
    @Bean
    public AnnotationSessionFactoryBean getSessionFactory()
    {
        AnnotationSessionFactoryBean asfb = new AnnotationSessionFactoryBean();
        asfb.setDataSource(getDataSource());
        asfb.setHibernateProperties(getHibernateProperties());        
        asfb.setPackagesToScan(new String[]{"com.sivalabs"});
        return asfb;
    }

    @Bean
    public Properties getHibernateProperties()
    {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", hibernateDialect);
        properties.put("hibernate.show_sql", hibernateShowSql);
        properties.put("hibernate.hbm2ddl.auto", hibernateHbm2ddlAuto);
        
        return properties;
    }
    
}

Create an Entity User as follows:
package com.sivalabs.springmvc.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * @author SivaLabs
 *
 */

@Entity
public class User
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String name;
    private String address;
    
    public User()
    {
    }
    public User(Integer id, String name, String address)
    {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    
    @Override
    public String toString()
    {
        return "User [address=" + address + ", id=" + id + ", name=" + name+ "]";
    }
    public Integer getId()
    {
        return id;
    }
    public void setId(Integer id)
    {
        this.id = id;
    }
    public String getName()
    {
        return name;
    }
    public void setName(String name)
    {
        this.name = name;
    }
    public String getAddress()
    {
        return address;
    }
    public void setAddress(String address)
    {
        this.address = address;
    }
    
}
Create UserRepository to perform DB operations using Hibernate.
package com.sivalabs.springmvc.repositories;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.sivalabs.springmvc.entities.User;

/**
 * @author SivaLabs
 *
*/

@Transactional
@Repository
public class UserRepository
{
    @Autowired
    private HibernateTemplate hibernateTemplate;
    
    public List<User> getAllUsers()
    {
        return this.hibernateTemplate.loadAll(User.class);
    }
    
    public Integer createUser(User user)
    {
        User mergeUser = this.hibernateTemplate.merge(user);
        return mergeUser.getId();
    }
}

Create a UserService class which is responsible for performing User operations.
package com.sivalabs.springmvc.services;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.sivalabs.springmvc.entities.User;
import com.sivalabs.springmvc.repositories.UserRepository;

/**
 * @author SivaLabs
 *
 */

@Service
public class UserService
{
    @Autowired
    private UserRepository userRepository;
    
    public List<User> getAllUsers()
    {
        return this.userRepository.getAllUsers();
    }
    
    public Integer createUser(User user)
    {
        return this.userRepository.createUser(user);
    }
}

Now let us create ApplicationContext from AppConfig.java and test the functionality.
package com.sivalabs.test;

import java.util.List;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.sivalabs.springmvc.entities.User;
import com.sivalabs.springmvc.services.UserService;

public class ContainerTest
{
    public static void main(String[] args)
    {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
        ctx.scan("com.sivalabs");//This will load the configured components UserService, UserRepository, 
        ctx.refresh();
        
        System.out.println(ctx);
        UserService userService = ctx.getBean("userService", UserService.class);
        
        List<User> allUser = userService.getAllUsers();
        for (User u : allUser)
        {
            System.out.println(u);
        }
        
        User user = new User(null, "K.siva reddy", "hyderabad");
        Integer id = userService.createUser(user);
        System.out.println("Newly created User Id="+id);
        allUser = userService.getAllUsers();
        for (User u : allUser)
        {
            System.out.println(u);
        }
    }

}
See how application development is much more easier now with Annotations.

11 comments:

  1. This is a wonderful code obfuscation technique you have demonstrated. When you distribute a jar or war file from this, it will be totally incomprehensible to recipients. What a wonderful improvement from the pre-obfuscation days when a customer could expand a war file and see all those easy to understand, self-documenting XML configuration files. This is really great what you have done to increase the difficulty of understanding a complex system unless the java source code is at hand.

    ReplyDelete
  2. Thanks for your comment :-)

    But I missed to convey my main intention of going to use Annotation approach entirely instead of simple xml based approach.

    Here it is:

    I had a discussion with one of my friend about extending PropertyPlaceholderConfigurer and provide additional utility methods like JConfig's centralized Configuration manager.
    All over the Annotation based Spring examples use xml based PropertyPlaceholderConfigurer configuration. So I thought of just telling there is a way to configure customized PropertyPlaceholderConfigurer through Annotations.

    I am neither fan of Annotations nor XML. And I am not a master of obfuscation techniques too :-)
    If you ask me to choose the approach, I will choose XML for non-frequently changing configuration and Annotations for Controller mappings and all.

    ReplyDelete
    Replies
    1. Great article, keep up the great work. being new to Spring can you elaborate on mobiusinversion problem as I get the same problem. why are we having this problem.

      Delete
  3. Hi,

    I think your experiment is really good to prove that xml in spring is totally optional. I prefer Annotations than XML. I am a new comer to Spring, would you be willing to post another example, but with a web approach and using open session in view instead of transactions in the repository?

    Thx, and good job again.
    Thiago

    ReplyDelete
  4. the import AppConfig is confusing, because it does not depend on it. the following AllConfigs.java will import then in right order (the import in AppConfig is now removed)


    @Import({DataAccessRepositoryConfig.class, AppConfig.class})
    @Configuration
    public class AllConfigs {

    }

    ReplyDelete
  5. Is there anyway to download code for this example?

    ReplyDelete
  6. Love it! I, too, would like to see a web version of this example.

    ReplyDelete
  7. I got this error when running the container test. Any advice?

    Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private org.springframework.orm.hibernate3.HibernateTemplate com.mobiusinversion.web.repositories.UserRepository.hibernateTemplate; nested exception is java.lang.NoClassDefFoundError: [Lorg/hibernate/engine/FilterDefinition;
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:292)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1185)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:304)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)

    ReplyDelete