JPA with UUID

Published: Wed, 12 Jun 2013

I wanted to have a data package containing only JPA annotations.

I created a base class specifying the keytype (I use UUID and Long based Entities):

package com.famvdploeg.data;

import java.io.Serializable;

/**
* Absolute base class for persistable classes.
* Contract forces get and set methods for id.
* @author wytze
* @param <KeyType>
*/
public abstract class Persistable<KeyType extends Serializable> {
public abstract KeyType getId();

	public abstract void setId(KeyType id);
}
package com.famvdploeg.data;

import java.util.UUID;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

@MappedSuperclass
public abstract class UuidBasedEntity extends Persistable<UUID> {

	@Id
	private UUID id;

	@Override
	public UUID getId() {
		return id;
	}

	@Override
	public void setId(UUID id) {
		this.id = id;
	}
}

Sample entity:

package com.famvdploeg.data;

import java.io.Serializable;
import java.util.UUID;
import javax.persistence.Entity;

@Entity
public class Sample extends UuidBasedEntity implements Serializable {

	private String someExampleProperty;

        /* ... getters and setters */
}

All these entities go in one separate jar.

Next I create another package using the entity jar.

package com.famvdploeg.dao;

import com.famvdploeg.data.Persistable;
import java.io.Serializable;

public interface GenericRepository<Entity extends Persistable> {
public void persist(Entity e);
public Entity findById(Serializable id);
public void merge(Entity t);
public void remove(Serializable id);
public Entity saveOrUpdate(Entity entity);
}

And a generic implementation of this dao: (Spring is used to inject the persistencecontext)

package com.famvdploeg.dao.jpa;

import java.io.Serializable;
import com.famvdploeg.dao.GenericRepository;
import com.famvdploeg.data.Persistable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

public class GenericJpaRepository<Entity extends Persistable> implements GenericRepository<Entity> {

	@PersistenceContext
	protected EntityManager em;

	@Override
	public void persist(Entity e) {
		em.persist(e);
	}

	@Override
	public Entity findById(Serializable id) {
		return em.find(returnEntityClass(), id);
	}

	@Override
	public void merge(Entity e) {
		em.merge(e);
	}

	@Override
	public void remove(Serializable id) {
		em.remove(findById(id));
	}

	@Override
	public void flush() {
		em.flush();
	}

	@Override
	public Entity saveOrUpdate(Entity entity) {
		if (entity.getId() == null) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

	protected List<Entity> findAll() {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(returnEntityClass());

		return em.createQuery(cq).getResultList();
	}

	protected Entity findByProperty(String property, Object value) {
		try {
			return em.createQuery(createQueryByProperty(property, value)).getSingleResult();
		} catch (NoResultException ex) {
			return null;
		}
	}

	protected List<Entity> findAllByProperty(String property, Object value) {
		return em.createQuery(createQueryByProperty(property, value)).getResultList();
	}

	protected Entity findByProperties(Map<String, Object> properties) {
		try {
			return em.createQuery(createQueryByProperties(properties)).getSingleResult();
		} catch (NoResultException ex) {
			return null;
		}
	}

	protected List<Entity> findAllByProperties(Map<String, Object> properties) {
		return em.createQuery(createQueryByProperties(properties)).getResultList();
	}

	public Class<Entity> returnEntityClass() {
		ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
		return (Class<Entity>) genericSuperclass.getActualTypeArguments()[0];
	}

	private CriteriaQuery<Entity> createQueryByProperty(String property, Object value) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(returnEntityClass());
		Root<Entity> root = cq.from(returnEntityClass());
		cq = cq.where(cb.equal(root.get(property), value));
		return cq;
	}

	private CriteriaQuery<Entity> createQueryByProperties(Map<String, Object> properties) {
		CriteriaBuilder cb = em.getCriteriaBuilder();
		CriteriaQuery<Entity> cq = cb.createQuery(returnEntityClass());
		Root<Entity> root = cq.from(returnEntityClass());
		for (Entry<String, Object> entry : properties.entrySet()) {
			cq = cq.where(cb.equal(root.get(entry.getKey()), entry.getValue()));
		}
		return cq;
	}
}

Now I want to use hibernate to map my UUID’s as PostgreSQL uuid type. We will add a mapping to do so.

(location: com/famvdploeg/data/CustomTypes.hbm.xml, place in DAO/Repository jar)

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--
http://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/types.html
Section 6.5 covers type registry and registerTypeOverride method on the Configuration object,
typedefs are the preferred way of registering custom types
-->
<typedef name="java.util.UUID" class="org.hibernate.type.UUIDCharType" />

    <!--
        I want to store clob values as text and not as materialized clobs so we override that here.
        Otherwise the database will show long values when using an SQL tool to query the database.
    -->
    <typedef name="materialized_clob" class="org.hibernate.type.TextType" />
	<!-- Possible values:
	<typedef name="java.util.UUID" class="org.hibernate.type.UUIDBinaryTypee" />
	<typedef name="java.util.UUID" class="org.hibernate.type.UUIDCharType" />
	<typedef name="java.util.UUID" class="org.hibernate.type.PostgresUUIDType" />
	-->
</hibernate-mapping>

Next we fill the persistence.xml

(location: META-INF/persistence.xml)

<?xml version="1.0" encoding="UTF-8"?>
<persistence
xmlns='http://java.sun.com/xml/ns/persistence'
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:schemaLocation='http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd'
version="2.0">

	<persistence-unit name="jpa-example-postgres" transaction-type="RESOURCE_LOCAL">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>

		<class>com.famvdploeg.data.Sample</class>

		<properties>
			<!--<property name="hibernate.archive.autodetection" value="class, hbm"/>-->
			<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL82Dialect" />
                        <!-- Use this only for testing!
			<property name="hibernate.hbm2ddl.auto" value="create-drop" />
                        -->
                        <!-- Nicer naming of tables with underscores fooName -> foo_name -->
			<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />

			<!-- Connection properties, moved to datasource (@see applicationContext.xml)
			<property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/jpa_example" />
			<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
			<property name="hibernate.connection.username" value="example" />
			<property name="hibernate.connection.password" value="example" />
			-->

			<!-- c3p0 connection pooling, setup when creating datasource (@see com.mchange.v2.c3p0.ComboPooledDataSource)
			<property name="hibernate.c3p0.min_size">5</property>
			<property name="hibernate.c3p0.max_size">20</property>
			<property name="hibernate.c3p0.timeout">300</property>
			<property name="hibernate.c3p0.max_statements">50</property>
			<property name="hibernate.c3p0.idle_test_period">3000</property>
			-->
		</properties>

                <!-- The specific mapping for UUID's is added here -->
		<mapping-file>com/famvdploeg/data/CustomTypes.hbm.xml</mapping-file>
	</persistence-unit>
</persistence>

And for completeness the applicationContext.xml for Spring

<?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:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"

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

	<!-- In production: <bean class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer"> -->
	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
		<!-- Test properties. Should be filled from Context.xml in production -->
		<property name="properties">
			<props>
				<!--
				<prop key="jpa-example-jdbc-url">jdbc:h2:mem:jpa_example</prop>
				<prop key="jpa-example-jdbc-username">sa</prop>
				<prop key="jpa-example-jdbc-password">sa</prop>
				<prop key="jpa-example-jdbc-driver">org.h2.Driver</prop>
				-->
				<prop key="jpa-example-jdbc-url">jdbc:postgresql://localhost:5432/jpa_example</prop>
				<prop key="jpa-example-jdbc-username">example</prop>
				<prop key="jpa-example-jdbc-password">example</prop>
				<prop key="jpa-example-jdbc-driver">org.postgresql.Driver</prop>
			</props>
		</property>
	</bean>

	<!-- Use pooled in production, @see com.mchange.v2.c3p0.ComboPooledDataSource -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="url" value="${jpa-example-jdbc-url}" />
		<property name="username" value="${jpa-example-jdbc-username}" />
		<property name="password" value="${jpa-example-jdbc-password}" />
		<property name="driverClassName" value="${jpa-example-jdbc-driver}" />
	</bean>

	<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="persistenceUnitName" value="jpa-example-postgres" />
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>

	<tx:annotation-driven transaction-manager="transactionManager" />

	<context:annotation-config />
</beans>

It is also possible to store the UUID as a char(36) to do this we need to change the type in the CustomTypes.hbm.xml to org.hibernate.type.UUIDCharType. We can then modify the mapping from the data jar through adding an orm.xml to the META-INF folder. It will normally create a varchar(255) column but that is useless. We want to specify the use of an char(36) column as UUID’s are stored as Strings of a fixed length of 36 characters.

<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings
xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd"
version="2.0">

	<package>com.famvdploeg.data</package>

	<mapped-superclass class="UuidBasedEntity" metadata-complete="false">
		<attributes>
			<id name="id">
				<column column-definition="char(36)" />
			</id>
		</attributes>
	</mapped-superclass>
</entity-mappings>

That’s it! Awesome! Clean separation of JPA and Hibernate.