Spring Persistence with Hibernate

03 September 2014

After I prototyped a Spring standalone application with a deployment-friendly runnable jar, I want to prototype another Spring standalone application with added complexity, Hibernate persistence I mean. I'm going to use H2 to avoid 3rd party database dependency.

Just like my previous post, I'm going to use maven-archetype-generate and will add Spring dependencies later on.

$ mvn archetype:generate -DgroupId=com.manalo.prototype -DartifactId=persistence -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom
[INFO]
[INFO] maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom
[INFO]
[INFO] --- maven-archetype-plugin:2.2:generate (default-cli) @ standalone-pom ---
[INFO] Generating project in Batch mode
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: com.manalo.prototype
[INFO] Parameter: packageName, Value: com.manalo.prototype
[INFO] Parameter: package, Value: com.manalo.prototype
[INFO] Parameter: artifactId, Value: persistence
[INFO] Parameter: basedir, Value: /home/drmanalo/workspace
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: /home/drmanalo/workspace/persistence
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 52.401s
[INFO] Finished at: Thu Sep 04 23:17:16 BST 2014
[INFO] Final Memory: 14M/150M
[INFO] ------------------------------------------------------------------------

mvn eclipse:eclipse

$ mvn eclipse:eclipse
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building persistence 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] maven-eclipse-plugin:2.9:eclipse (default-cli) @ persistence
[INFO]
[INFO] maven-eclipse-plugin:2.9:eclipse (default-cli) @ persistence
[INFO]
[INFO] --- maven-eclipse-plugin:2.9:eclipse (default-cli) @ persistence ---
[INFO] Using Eclipse Workspace: /home/drmanalo/workspace
[INFO] Adding default classpath container: org.eclipse.jdt.launching.JRE_CONTAINER
[INFO] Not writing settings - defaults suffice
[INFO] Wrote Eclipse project for "persistence" to /home/drmanalo/workspace/persistence.
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.325s
[INFO] Finished at: Thu Sep 04 23:23:07 BST 2014
[INFO] Final Memory: 9M/150M
[INFO] ------------------------------------------------------------------------

pom.xml

Here's my complete pom.xml with added dependencies apart from Spring persistence.

<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>com.manalo.prototype</groupId>
	<artifactId>persistence</artifactId>
	<packaging>jar</packaging>
	<version>1.0-SNAPSHOT</version>
	<name>persistence</name>
	<url>http://maven.apache.org</url>

	<properties>
		<spring.version>3.2.0.RELEASE</spring.version>
		<hibernate.version>4.1.9.Final</hibernate.version>
	</properties>

	<dependencies>
		<!-- Database -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>1.3.173</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-dbcp2</artifactId>
			<version>2.0.1</version>
		</dependency>
		<!-- Hibernate -->
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>
		<!-- Logging -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-orm</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.persistence</groupId>
			<artifactId>persistence-api</artifactId>
			<version>1.0</version>
		</dependency>
	</dependencies>

	<pluginRepositories>
		<pluginRepository>
			<id>onejar-maven-plugin.googlecode.com</id>
			<url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url>
		</pluginRepository>
	</pluginRepositories>

	<build>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>2.3.2</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
			<plugin>
				<groupId>org.dstovall</groupId>
				<artifactId>onejar-maven-plugin</artifactId>
				<version>1.3.0</version>
				<executions>
					<execution>
						<configuration>
							<mainClass>com.manalo.rabbitmq.App</mainClass>
						</configuration>
						<goals>
							<goal>one-jar</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

</project>

Spring Configurations

This contains database connection, connection pooling and Hibernate configurations. I defined the packages where Spring should look for its beans using context:component-scan elements.

<?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:context="http://www.springframework.org/schema/context"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:p="http://www.springframework.org/schema/p"
	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.xsd">

	<tx:annotation-driven />

	<context:component-scan base-package="com.manalo.dao" />
	<context:component-scan base-package="com.manalo.service" />

	<!-- Load application properties -->
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<property name="locations">
			<list>
				<value>classpath:application.properties</value>
			</list>
		</property>
	</bean>

	<!-- DB connection -->
	<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"
		destroy-method="close">
		<property name="driverClassName" value="${db.driver}" />
		<property name="url" value="${db.url}" />
		<property name="username" value="${db.user}" />
		<property name="password" value="${db.pass}" />
	</bean>

	<!-- Hibernate configuration -->
	<bean id="sessionFactory"
		class="org.springframework.orm.hibernate4.LocalSessionFactoryBean" >
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
				<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
				<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
				<prop key="hibernate.cache.use_second_level_cache">false</prop>
				<prop key="hibernate.cache.use_query_cache">false</prop>
				<prop key="hibernate.generate_statistics">false</prop>
				<prop key="hibernate.cache.use_structured_entries">false</prop>
			</props>
		</property>
		<property name="packagesToScan" value="com.manalo.domain" />
	</bean>

	<!-- Transaction manager -->
	<bean id="transactionManager"
		class="org.springframework.orm.hibernate4.HibernateTransactionManager">
		<property name="dataSource" ref="dataSource" />
		<property name="sessionFactory" ref="sessionFactory" />
	</bean>

</beans>

application.properties

I put this under src/main/resources which has to be added in your classpath in order for PropertyPlaceholderConfigurer to find it.

# Embedded H2 database
db.driver=org.h2.Driver
db.user=testuser
db.pass=password
db.host=localhost
db.port=3336
db.name=test
db.vendor=h2
db.schemaname=PUBLIC
db.url=jdbc:h2:mem:${db.name};MODE=MySQL;USER=${db.user};PASSWORD=${db.pass};DB_CLOSE_DELAY=-1;LOCK_TIMEOUT=10000;INIT=create schema if not exists test\

# Connection pool
db.pool.acquireincrement=1
db.pool.minpoolsize=1
db.pool.maxpoolsize=20
db.pool.maxidletime=14400

# Hibernate (ORM)
hibernate.hbm2ddl.auto=create
hibernate.show_sql=true
hibernate.dialect=org.hibernate.dialect.MySQLDialect

log4j.properties

As this is part of convention over configuration, you have to name this file as log4j.properties and should also be within your classpath.

# Root logger
log4j.rootLogger=INFO, file
log4j.logger.com.manalo=DEBUG, stdout
log4j.logger.org.springframework=INFO, stdout
log4j.logger.org.hibernate=INFO, stdout

# Direct application log messages to a file
log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=./logs/manalo.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p [%15.15t] %c - %m%n

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

Database Config

<?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:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-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
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
       http://www.springframework.org/schema/jdbc
       http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd">

	<!-- Use a web browser to access http://localhost:11111 to view H2 console
		that allows db queries to be run and be viewed. Parameters url, username
		and password should be same as JDBC connection. e.g. jdbc:h2:mem:test, testuser,
		password -->
	<bean id="org.h2.tools.Server-WebServer" class="org.h2.tools.Server"
		factory-method="createWebServer" depends-on="dataSource" init-method="start"
		lazy-init="false">
		<constructor-arg value="-web,-webPort,11111" />
	</bean>

	<jdbc:initialize-database data-source="dataSource">
		<jdbc:script location="classpath:/initialise.sql"></jdbc:script>
	</jdbc:initialize-database>

</beans>

initialise.sql

You will notice this file is being loaded using jdbc:script element.

CREATE TABLE user (
	id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  	username VARCHAR (32) NOT NULL,
  	password VARCHAR (255) NOT NULL,
  	UNIQUE (USERNAME)
);

Domain Object

package com.manalo.domain;

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

@Entity
@Table
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(nullable = false)
	private int id;

	@Column(nullable = false)
	private String username;

	@Column(nullable = false)
	private String password;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

}

Data Access Component

package com.manalo.dao;

import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.manalo.domain.User;

@Repository
public class UserDao {

	@Autowired
	private SessionFactory sessionFactory;

	public Session getSession() {
		return sessionFactory.getCurrentSession();
	}

	public Integer addUser(User user) {
		return (Integer) getSession().save(user);
	}

	public User getUserByUserName(String username) {

		String hql = "from User where username = :username";

		Query query = getSession().createQuery(hql);
		query.setString("username", username);

		return (User) query.uniqueResult();
	}

	@SuppressWarnings("unchecked")
	public List<User> getUsers() {
		Criteria criteria = getSession().createCriteria(User.class);
		return criteria.list();
	}

}

Service Layer

package com.manalo.service;

import java.util.List;

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

import com.manalo.dao.UserDao;
import com.manalo.domain.User;

@Service
@Transactional
public class UserManager {

	@Autowired
	private UserDao userDao;

	public int addUser(User user) {
		return userDao.addUser(user);
	}

	public User getUserByUserName(String username) {
		return userDao.getUserByUserName(username);
	}

	public List<User> getUsers() {
		return userDao.getUsers();
	}

}

App.java

package com.manalo.prototype;

import java.util.List;

import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.manalo.domain.User;
import com.manalo.service.UserManager;

public class App {

	public static void main(String[] args) {

		/**
		 * Order of xml files to be loaded is important
		 */
		final String[] CONFIG_FILES = { "database-init.xml", "spring-core.xml" };

		AbstractXmlApplicationContext context = new ClassPathXmlApplicationContext(CONFIG_FILES);

		UserManager userManager = context.getBean(UserManager.class);

		User user = new User();

		user.setUsername("drmanalo");
		user.setPassword("secret");

		userManager.addUser(user);

		user = userManager.getUserByUserName("drmanalo");

		System.out.println("Id:       " + user.getId());
		System.out.println("Username: " + user.getUsername());
		System.out.println("Password: " + user.getPassword());

		List<User> users = userManager.getUsers();

		System.out.println("Number of users: " + users.size());

		context.close();

	}
}

Running the application

Within Eclipse, right-click App.java -> Run As -> Java Application. You should see this within Eclipse's console. There are extra noises since log4j is doing its job.

Hibernate: drop table if exists User
Hibernate: create table User (id integer not null auto_increment, password varchar(255) not null, username varchar(255) not null, primary key (id))
23:06:01,825  INFO SchemaExport:405 - HHH000230: Schema export complete
Hibernate: insert into User (password, username) values (?, ?)
Hibernate: select user0_.id as id0_, user0_.password as password0_, user0_.username as username0_ from User user0_ where user0_.username=?
Id:       1
Username: drmanalo
Password: secret
Hibernate: select this_.id as id0_0_, this_.password as password0_0_, this_.username as username0_0_ from User this_
Number of users: 1

Download source