In this post you will learn one of the ways to create a layered data driven application using Hibernate and Spring 3. The architecture will go up from the database to the service layer, so it’s your choice how to do the presentation part. I will try to adhere to Spring’s best practices in the separation of layers, so the resulting architecture offers both a clear separation between the layers and little dependencies in the Spring framework.
Setting up
I use Maven to take care of the compiling and life-cycle of the project. You may use this pom.xml file as the starting point for this project. It basically defines the needed repositories and dependencies that will be used in this guide.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>tld.example</groupId> <artifactId>layeredarch-example</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>Layered Arch Example</name> <properties> <aspectj.version>1.6.6</aspectj.version> <commons-dbcp.version>1.2.2</commons-dbcp.version> <hibernate-annotations.version>3.4.0.GA</hibernate-annotations.version> <hibernate-core.version>3.3.2.GA</hibernate-core.version> <hsqldb.version>1.8.0.10</hsqldb.version> <javassist.version>3.7.ga</javassist.version> <log4j.version>1.2.15</log4j.version> <slf4j-log4j12.version>1.5.6</slf4j-log4j12.version> <springframework.version>3.0.0.RC1</springframework.version> </properties> <dependencies> <!-- Compile time dependencies --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> <exclusions> <exclusion> <groupId>javax.jms</groupId> <artifactId>jms</artifactId> </exclusion> <exclusion> <groupId>com.sun.jdmk</groupId> <artifactId>jmxtools</artifactId> </exclusion> <exclusion> <groupId>com.sun.jmx</groupId> <artifactId>jmxri</artifactId> </exclusion> <exclusion> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate-core.version}</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-annotations</artifactId> <version>${hibernate-annotations.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>org.springframework.orm</artifactId> <version>${springframework.version}</version> </dependency> <!-- Runtime dependencies --> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>${commons-dbcp.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>${hsqldb.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j-log4j12.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>jboss</groupId> <artifactId>javassist</artifactId> <version>${javassist.version}</version> <scope>runtime</scope> </dependency> </dependencies> <build> <finalName>layeredarch-example</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> <repositories> <!-- Legacy java.net repository --> <repository> <id>java-net</id> <url>http://download.java.net/maven/1</url> <layout>legacy</layout> </repository> <!-- JBoss repositories: hibernate, etc. --> <repository> <id>jboss</id> <url>http://repository.jboss.com/maven2</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>jboss-snapshot</id> <url>http://snapshots.jboss.org/maven2</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> <!-- SpringSource repositories --> <repository> <id>springsource-milestone</id> <url>http://repository.springsource.com/maven/bundles/milestone</url> </repository> <repository> <id>springsource-release</id> <url>http://repository.springsource.com/maven/bundles/release</url> </repository> <repository> <id>springsource-external</id> <url>http://repository.springsource.com/maven/bundles/external</url> </repository> </repositories> </project> |
Defining Entities
The Entities represent the domain of your project. They are simple JavaBean classes that also configure how this domain will be persisted to a database. They will be annotated with standard javax.persistence
annotations so there will be no dependence in neither Hibernate nor Spring.
The following User.java
is a simple Entity that represents an user in the application. In this example, for every user his name and age are stored along with an auto-generated ID that will identify every persisted user.
package tld.example.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class User { private Long id; private String name; private Integer age; public User() { } @Id @GeneratedValue public Long getId() { return this.id; } private void setId(Long id) { this.id = id; } @Column public String getName() { return this.name; } public void setName(String name) { this.name = name; } @Column public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } |
You may save this class in the tld.example.domain package, where you will also save all the additional Entities that you add.
DAO Layer
First layer up in the architecture, it’s the DAO layer. These objects take care of the operations needed to query the database in order to fetch, store and update your Entities. I defined the DAOs in an interface/implementation manner. It’s not only a good design practice, but it also helps Spring AOP.
The UserDao interface would be as follows:
package tld.example.dao; import tld.example.domain.User; public interface UserDao { public User findById(Long id); public User persistOrMerge(User user); } |
For the implementation, I have chosen to use Hibernate directly. Nevertheless it’s quite easy to change it to JPA and the provider you prefer. This is the HibernateUserDao class:
package tld.example.dao.impl; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import tld.example.dao.UserDao; import tld.example.domain.User; @Repository public class HibernateUserDao implements UserDao { @Autowired(required=true) private SessionFactory sessionFactory; public User findById(Long id) { return (User) this.sessionFactory.getCurrentSession().createQuery( "from User user where user.id=?").setParameter(, id) .uniqueResult(); } public User persistOrMerge(User user) { return (User) this.sessionFactory.getCurrentSession().merge(user); } } |
Take into account that there is no transaction management code in this layer. DAOs mission is to abstract the CRUD tasks from your service layer. Transactional logic will be one layer above.
Also, the code exhibits two Spring dependencies because annotations were used to configure the dependency injection of the application. You could easily remove them by moving this configuration to XML.
Service Layer
This is the highest layer of this example’s architecture. The service layer provides your application with transactional operations for your business logic. The idea behind this is that a service method is the smallest atomic operation your application will do in the database, so a service method either completes and the resulting database is in consistent status for your application, or rollbacks to its previous state (which should also be consistent).
Again, the services are split into an interface and an implementation. I defined the following simple UserService interface:
package tld.example.service; import tld.example.domain.User; public interface UserService { public User retrieveUser(Long id); public User createUser(User user); } |
And the implementation UserServiceImpl.java:
package tld.example.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import tld.example.dao.UserDao; import tld.example.domain.User; import tld.example.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired(required=true) private UserDao userDao; @Transactional public User createUser(User user) { return this.userDao.persistOrMerge(user); } @Transactional(readOnly=true) public User retrieveUser(Long id) { return this.userDao.findById(id); } } |
As you can see, when a service method will only perform read operations, you may tell so to Spring and it will be able to optimize the call (this is very useful when the backend is Hibernate).
A very important thing to note here. This is a very simple example with only one Entity and consequently only one DAO, so the Service is very simple. But with this layering, you may very well have Services that use more than one DAO and their functionality spans multiple Entities. The transactional part will take care of that, and you only need to design the Service methods right so they leave the data in the correct status.
Making it all work together
Now, the last step is to configure Hibernate and Spring to make it all work together. As you will see, thanks to the use of annotations very few config lines are needed.
Hibernate will be mostly managed by Spring, so the only configuration it needs is a pointer to the annotated Entities. In this case, only the User class. This is the hibernate.cfg.xml:
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <mapping class="tld.example.domain.User" /> </session-factory> </hibernate-configuration> |
On the Spring part, a bit more of configuration is needed to set up the declarative transactions. Hsqldb is used for the database. This is the applicationContext.xml file:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- Configure annotated beans --> <context:annotation-config /> <context:component-scan base-package="tld.example" /> <!-- DataSource: hsqldb file --> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:file:target/data/example" /> <property name="username" value="sa" /> <property name="password" value="" /> </bean> <!-- Hibernate --> <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource" /> <property name="configLocation"> <value>classpath:hibernate.cfg.xml</value> </property> <property name="configurationClass"> <value>org.hibernate.cfg.AnnotationConfiguration</value> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">create</prop> <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> </props> </property> </bean> <!-- Transaction management --> <tx:annotation-driven/> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans> |
Basically, the following is configured:
- Spring is told to scan all your classes under the tld.example package and configure the beans according to the annotations. This will create the UserDao and UserService singletons and inject the autowired fields.
- A DataSource is configured. It uses Apache DBCP for pooling, and hsqldb as Database (both are included in the project dependencies).
- Spring will inject Hibernate’s SessionFactory to the DAOs. The
mySessionFactory
bean is all what is needed to do so correctly. - And finally, the configuration needed for the declarative transaction management. This code will ensure that all the @Transactional methods in the service layer either run a full transaction or roll-back in case an exception occurs.
And that’s it Time for coding all your Entities, DAOs and Services now. There are lot’s of ways in which you can customize or improve this setup (use JPA, configure Spring’s exception translator, bean validation, etc.), but the important part is that the layering in the architecture allows to do so easily.