Introduction

This tutorial gives a quick overview of how to get Hibernate Spatial working. We will develop a simple application that stores, and retrieves some simple data objects. The data objects are "special" in that they have a property of type Geometry.

This tutorial assumes that you are familiar with Hibernate, and the basic concepts of working with geographic data. It is also based on the Hibernate Tutorial and uses the same examples. If you haven't read the Hibernate Tutorial, and are new to Hibernate, then please read it before proceeding here.

The Hibernate Tutorial uses the HSQLDB in-memory database to keep things simple. Since Hibernate Spatial doesn't (yet) support HSQLDB, we have no such luck. We require a postgis database. For information on how to create a postgis database, look here .

Pre-requisites

To use Hibernate Spatial you first need to get Hibernate and the Java Topology Suite. Hibernate provides the ORM functionality, and the Java Topology Suite provides the Geometry Type (and related types). Hibernate Spatial is the glue between them.

You can find Hibernate at http://www.hibernate.org/ . At the Hibernate site you can also find a lot of resources about how to install and use Hibernate. Be sure to get all Hibernate dependencies.

The Java Topology Suite (JTS) can be found at http://www.vividsolutions.com/jts/jtshome.htm . It is a relatively small but rather complete library for all types of Geometries and it conforms to the Simple Feature Specification of the OpenGIS Consortium.

Make sure that you use Hibernate v.3.2 and JTS v.1.8+ (support for Hibernate v3.3 is forthcoming). Also remember that Hibernate Spatial requires a java 1.5+ runtime. We will also assume that you have a working Postgis database ready.

Creating a Spatially-Enabled EventManager

We will create a small application to store and retrieve events we want to attend. (This is the same use case as in the Hibernate Tutorial).

We first set up our development directory and put all the libraries in the ./lib directory. This is the jts-1.8.jar, hibernate3.jar and the Hibernate dependencies (the rest). Our development directory now looks like this:

		.
		+lib
			antlr-2.7.6.jar
			commons-collections-2.1.1.jar
			jta.jar
			asm-attrs.jar
			commons-logging-1.0.4.jar
			jts-1.8.jar
			asm.jar
			dom4j-1.6.1.jar
			log4j-1.2.11.jar
			cglib-2.1.3.jar
			hibernate3.jar
		
Now we need to add the Hibernate Spatial libraries to the ./lib directory. Specifically, we need the hibernate-spatial core library, and the provider library for Postgis. We use the (at the time of writing) latest releases from the download->releases page. Now our development directory looks like:
		.
		+lib
			antlr-2.7.6.jar
			...
			hibernate3.jar
			hibernate-spatial-postgis-1.0-M2.jar 
			hibernate-spatial-1.0-M2.jar
		

Finally, we also need a recent postgresql JDBC Driver together with its postgis extensions. These can be found here and here , resp.

		.
		+lib
			antlr-2.7.6.jar
			...
			hibernate3.jar
			hibernate-spatial-postgis-1.0-20070920.111959-1.jar 
			hibernate-spatial-1.0-20070920.111959-1.jar
			postgis.jar
			postgresql-8.2-506.jdbc3.jar
		

The Event Class

Our persistent class is the Event class. Since this class contains a geometry-valued property (a property of type Geometry), its instances are geographic objects, or features.


package events;

import java.util.Date;
import com.vividsolutions.jts.geom.Point;

public class Event {
    private Long id;
    private String title;
    private Date date;
    private Point location;

    public Event() {}

    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
    
    public Point getLocation(){
    	return this.location;
    }
    
    public void setLocation(Point location){
    	this.location = location;
    }
}
		
We put this file in the ./src directory of the development directory. So now we have
		.
		+lib
		+src
			+event
				Event.java
		

The Mapping file

To map this to the class, we create the following Hibernate Mapping file.

		 
<hibernate-mapping>
	<class name="events.Event" table="EVENTS">
		<id name="id" column="EVENT_ID">
			<generator class="native"/>
		</id>
		<property name="date" type="timestamp" 
			column="EVENT_DATE"/>
		<property name="title"/>
		<property name="location" 
			type="org.hibernatespatial.GeometryUserType" 
			column="LOC"/>
	</class>
</hibernate-mapping>
	
	
Hibernate Spatial provides the GeometryUserType that enables Hibernate to store the location property properly.

We save this mapping file as Event.hbm.xml along-side the java source file. So now we have

		.
		+lib
		+src
			+event
				Event.java
				Event.hbm.xml
		

The Hibernate Configuration

We proceed with the hibernate configuration file. The only difference w.r.t. normal Hibernate configurations files is with the dialect property. Hibernate Spatial extends the Hibernate Dialects so that the spatial features of the database are available within HQL and the SpatialCriteria (see below). So instead of using the (in our case) PostgreSQLDialect, we use Hibernate Spatial's extension of that dialect which is the PostGISDialect. Our hibernate.cfg.xml looks like this:


<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.postgresql.Driver</property>
        <property name="connection.url">jdbc:postgresql://localhost:5432/events</property>
        <property name="connection.username">postgres</property>
        <property name="connection.password"></property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SPATIAL SQL dialect -->
        <property name="dialect">org.hibernatespatial.postgis.PostgisDialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">create</property>

        <mapping resource="events/Event.hbm.xml"/>

    </session-factory>

</hibernate-configuration>		


		
As usual, we store this in the ./src directory.

Note that this configuration file means that Hibernate will connect to the "events" database on localhost, with username "postgres" and no password. (on the test system, postgres requires no password). You may need to change these values depending on your set-up.

Also notice that the "hbm2dll.auto" property is activated. This will re-create the database everytime the application is run (more precisely when the Hibernate SessionFactory is run).

Building with Ant

To ensure we can quickly build and test and test the application we willl use the Ant build system. You can learn about Ant here . We can use the exact same Ant build file as is used in the Hibernate Tutorial.


<project name="hibernate-tutorial" default="compile">

    <property name="sourcedir" value="${basedir}/src"/>
    <property name="targetdir" value="${basedir}/bin"/>
    <property name="librarydir" value="${basedir}/lib"/>

    <path id="libraries">
        <fileset dir="${librarydir}">
            <include name="*.jar"/>
        </fileset>
    </path>

    <target name="clean">
        <delete dir="${targetdir}"/>
        <mkdir dir="${targetdir}"/>
    </target>

    <target name="compile" depends="clean, copy-resources">
      <javac srcdir="${sourcedir}"
             destdir="${targetdir}"
             classpathref="libraries"/>
    </target>

    <target name="copy-resources">
        <copy todir="${targetdir}">
            <fileset dir="${sourcedir}">
                <exclude name="**/*.java"/>
            </fileset>
        </copy>
    </target>

</project>

		
This build file should be saved as build.xml and saved in the root of the development directory. If we run Ant from this directory, it should compile all sources and put them in the ./bin directory, together with the Hibernate configuration and mapping files.

The HibernateUtil helper

The HibernatUtil class creates the Hibernate SessionFactory for the application, and provides a getter to it. (The code below is copied from the Hibernate Tutorial without change).
package util;

import org.hibernate.*;
import org.hibernate.cfg.*;

public class HibernateUtil {

    private static final SessionFactory sessionFactory;

    static {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

}		
		

The EventManager

We are now ready to write a first version of the main application class EventManager.

package events;
import org.hibernate.Session;

import java.util.Date;

import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.io.ParseException;

import util.HibernateUtil;

public class EventManager {

    public static void main(String[] args) {
        EventManager mgr = new EventManager();

        if (args[0].equals("store")) {
            mgr.createAndStoreEvent("My Event", new Date(), args[1]);
        }

        HibernateUtil.getSessionFactory().close();
    }

    private void createAndStoreEvent(String title, Date theDate, String wktPoint ) {

        //First interpret the WKT string to a point
        WKTReader fromText = new WKTReader();
        Geometry geom = null;
        try{
                geom = fromText.read(wktPoint);
        } catch (ParseException e){
                throw new RuntimeException("Not a WKT string:" + wktPoint);
        }
        if (!geom.getGeometryType().equals("Point")){
                throw new RuntimeException("Geometry must be a point. Got a " + geom.getGeometryType());
        }
        
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();

        session.beginTransaction();

        Event theEvent = new Event();
        theEvent.setTitle(title);
        theEvent.setDate(theDate);
        theEvent.setLocation((Point)geom);
        session.save(theEvent);

        session.getTransaction().commit();
    }
}		
		
We modified the EventManger implementation of the Hibernate Tutorial so that it stores a point for the event. The point is given as a String in the Well-Known Text (WKT) format. See the JTS WTKReader JavaDoc for more information about the WTK format.

The development directory now looks like this.

		.
		build.xml
		+lib
			...libraries...
		+bin
			... compiled classes ...
		+src
			hibernate.cfg.xml
			+event
				Event.java
				Event.hbm.xml
				EventManager.java
			+util
				HibernateUtil.java
		

To easily run the EventManager we add a callable target to the Ant build file.


<target name="run" depends="compile">
    <java fork="true" classname="events.EventManager" classpathref="libraries">
        <classpath path="${targetdir}"/>
        <arg value="${action}"/>
        <arg value="${geom}"/>
    </java>
</target>

	 	
We can now run the program using Ant as follows:
 $ ant run -Daction=store -Dgeom="POINT(10 15)"
		
This will create the events table, add a new event with the date set to today, the title to "My Title", and the point to coordinates (10,15).

We can check the content of the events table using the astext() function that converts the geometries to their WKT representation.
	 
events=# select title, astext(loc) from events;	 	

  title   |    astext    
----------+--------------
 My Event | POINT(10 15)
(1 row)
	 	

Spatial Queries

We will now modify the EventManager by adding an action to list all events within a certain area. This will show how to use Hibernate Spatial for spatial querying.

Important: comment out the hbm2ddl.auto property in the hibernate.cfg.xml so that the event table is not deleted each time we run the EventManager.

Here is the addition to the main method in the EventManager that implements the "find" action.


...
        if (args[0].equals("store")) {
            mgr.createAndStoreEvent("My Event", new Date(), args[1]);
        }else if (args[0].equals("find")){
            List events = mgr.find(args[1]);
            for (int i = 0; i < events.size(); i++ ){
	            Event event = (Event)events.get(i);
                System.out.println("Event: " + event.getTitle() +
                                ", Time: " + event.getDate() +
                                ", Location: " + event.getLocation());
            }
        }	
...

		
We also need to implement the find(String filter) method.

The find method takes a WKT string that represents a polygon, and searches the events table for all events that are located within this polygon.

Here is the code.


...
//New imports
import org.hibernate.Criteria;
import java.util.List;
import org.hibernatespatial.criterion.SpatialRestrictions;
...


    private List find(String wktFilter){
        WKTReader fromText = new WKTReader();
        Geometry filter = null;
        try{
                filter = fromText.read(wktFilter);
        } catch(ParseException e){
                throw new RuntimeException("Not a WKT String:" + wktFilter);
        }
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        System.out.println("Filter is : " + filter);
        Criteria testCriteria = session.createCriteria(Event.class);
        testCriteria.add(SpatialRestrictions.within("location", null, filter));
        List results = testCriteria.list();
        session.getTransaction().commit();
        return results;
    }
...


		
		

The SpatialRestrictions class provides utility methods to create Criterion instances for spatial queries. In our example the WITHIN spatial operator is used to query all events with a location within a specified filter geometry.

The witin method (as most other methods of SpatialRestrictions) takes three parameters. The first is the name of the Geometry-valued property on which the filter is applied; the second is a spatial filter that may be used in a quick spatial-indexed based filter, and the third is the filter geometry. The second parameter is actually optional and can be set to null. If you use it, it will make the query much faster if you have a spatial index and the database expects a specific index-based filter expression (which is the case for PostGIS, but not Oracle). (Don't forget that in this tutorial we haven't actually created a spatial index!)

If now run:

$ ant run -Daction=find -Dgeom="POLYGON((1 1,20 1,20 20, 1 20, 1 1))"		
		
We get

	 ...
     [java] Filter is : POLYGON ((1 1, 20 1, 20 20, 1 20, 1 1))
     [java] Hibernate: select this_.EVENT_ID as EVENT1_0_0_, this_.EVENT_DATE as EVENT2_0_0_, 
     		this_.title as title0_0_, this_.LOC as LOC0_0_ 
     		from EVENTS this_ 
     		where (this_.LOC && ?  AND   within(this_.LOC, ?))
     [java] Event: My Event, Time: 2007-11-01 19:04:08.008, Location: POINT (10 15)	

		
The first line is the Filter that is echoed to System.out, the second line is the SQL generated by Hibernate (with the help of Hibernate Spatial). This is shown because the show_sql property is set in the configuration file. The last line is the output generated by the find action.

If you examine the generated SQL, you see how the SpatialRestrictions.within generates the fragment (this_.LOC && ? AND within(this_.LOC, ?)). The first part is the Postgis "Overlaps" operator that is used for the first stage filter, the second part is the precise within function as defined by the OGC Simple Features Specification.

An alternative approach is using HQL. The Postgis Dialect registers functions and operators specified in the the Simple Features for SQL specification. So we could write.

		...
		Query q = session
				.createQuery("from Event where within(location, ?) = true");
		Type geometryType = new CustomType(GeometryUserType.class, null);
		q.setParameter(0, filter, geometryType);
		List result = q.list();
		...
		
This is pretty much like any other HQL query in Hibernate, with the (slightly annoying) complication of having to create a Type instance for the GeometryUserType.

But I use Oracle, so what do I do?

Suppose we want to implement the EventManager on top of Oracle, how do we proceed? We need to change the following items

  1. Replace the hibernate-spatial-postgis-*.jar provider with the hibernate-spatial-oracle-*.jar provider.
  2. Replace the Postgresql Driver with the Oracle JDBC Driver.
  3. Remove the postgis.jar
  4. Modify the hibernate.cfg.xml configuration file so that it points to a suitable Oracle database, uses the Oracle JDBC driver, and has OracleSpatial10gDialect as dialect.
... and your done!

.. and what about MySQL

Recent versions of MySQL have (limited) support for spatial types. To get Hibernate Spatial to work with MySQL, change the following items.

  1. Replace the hibernate-spatial-postgis-*.jar provider with the hibernate-spatial-mysql-*.jar provider.
  2. Replace the Postgresql Driver with the MySQL JDBC Driver.
  3. Remove the postgis.jar
  4. Modify the hibernate.cfg.xml configuration file so that it points to a suitable MySQL database, uses the MySQL JDBC driver, and has MySQLSpatialDialect as dialect.

Please note that MySQL's implementation of the spatial types is incomplete at this time. Certain functions of Hibernate Spatial will fail at this time.

The MySQLSpatialDialect extends the vanilla MySQLDialect and therefore only supports the MyISAM tables.

Can I Use JPA Annotations with Hibernate Spatial?

In stead of using *.hbm.xml mapping files, you can also use JPA annotations. Well almost: JPA doesn't support spatial objects, and doesn't have an extension mechanism to add new types. So using only JPA annotations won't work. Fortunately, the Hibernate Annotations implementation of JPA does have a means for mapping UserTypes.

Let's modify the code to use annotations. First, we need to add the Hibernate Annotations implementation to our lib. We download the Hibernate Annotations package and put the ejb3-persistence.jar, hibernate-annotations.jar and hibernate-commons-annotations.jar in the lib directory. So that it becomes:

		.
		+lib
			antlr-2.7.6.jar
			commons-collections-2.1.1.jar
			jta.jar
			asm-attrs.jar
			commons-logging-1.0.4.jar
			jts-1.8.jar
			asm.jar
			dom4j-1.6.1.jar
			ejb3-persistence.jar
			log4j-1.2.11.jar
			cglib-2.1.3.jar
			hibernate3.jar
			hibernate-commons-annotations.jar
			hibernate-annotations.jar
			hibernate-spatial-postgis-1.0-M2.jar 
			hibernate-spatial-1.0-M2.jar
		

We now change the source code of the Events class so that it looks like this

package events;

import java.util.Date;

import javax.persistence.*;
import org.hibernate.annotations.Type;

import com.vividsolutions.jts.geom.Point;

@Entity
@Table(name="EVENTS")
public class Event {
	
	@Id @GeneratedValue
	@Column(name="EVENT_ID")
    private Long id;
	
    @Column(name="TITLE")
	private String title;
    
    @Column(name="EVENT_DATE")
    private Date date;
    

    @Column(name="LOC")
     @Type(type="org.hibernatespatial.GeometryUserType")
	private Point location;

    public Event() {}

    public Long getId() {
        return id;
    }

    private void setId(Long id) {
        this.id = id;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }
	
	public Point getLocation(){
		return this.location;
	}
	
	public void setLocation(Point location){
		this.location = location;
	}
}
		

Note that for the Geometry field we needed to use the Hibernate-specific @Type annotation.

Next, we replace the mapping resource by replacing the following line from the hibernate.cfg.xml

<mapping resource="events/Event.hbm.xml"/>
		

with a declaration of mapped classes.

<mapping class="events.Event">

Finally, we change the HibernateUtil so it uses an AnnotationConfiguration object. Change the line:

...
sessionFactory = new Configuration().configure().buildSessionFactory();
...

needs to change to

...
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
...

Now the operations should work as before.