Rev 87 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?xml version="1.0" encoding="UTF-8"?><document><properties><author>Karel Maesen</author><title>Tutorial</title></properties><body><section name="Introduction"><p>This tutorial gives a quick overview of how to getHibernate Spatial working. We will develop a simpleapplication that stores, and retrieves somesimple data objects. The data objects are "special" inthat they have a property of type Geometry.</p><p>This tutorial assumesthat you are familiar with Hibernate, and the basicconcepts of working with geographic data. It is also based on the<a href="http://www.hibernate.org/hib_docs/v3/reference/en/html/tutorial.html">Hibernate Tutorial</a>and uses the same examples. If youhaven't read the Hibernate Tutorial, and are new to Hibernate, then<strong>please read it before proceeding here</strong>.</p><p><p>The Hibernate Tutorial uses the HSQLDB in-memorydatabase to keep things simple. Since HibernateSpatial doesn't (yet) support HSQLDB, we have nosuch luck. We require a postgis database. Forinformation on how to create a postgis database, look<a href="http://postgis.refractions.net/docs/">here</a>.</p></p></section><section name="Pre-requisites"><p>To use Hibernate Spatial you first need to get Hibernateand the Java Topology Suite. Hibernate provides the ORMfunctionality, and the Java Topology Suite provides theGeometry Type (and related types). Hibernate Spatial isthe glue between them.</p><p>You can find Hibernate at<a href="http://www.hibernate.org/">http://www.hibernate.org/</a>. At the Hibernate site you can also find a lot ofresources about how to install and use Hibernate. Besure to get all Hibernate dependencies.</p><p>The Java Topology Suite (JTS) can be found at<ahref="http://http://www.vividsolutions.com/jts/jtshome.htm">http://www.vividsolutions.com/jts/jtshome.htm</a>. It is a relatively small but rather complete libraryfor all types of Geometries and it conforms to theSimple Feature Specification of the OpenGIS Consortium.</p><p>Make sure that you use recent versions (Hibernate v.3.2or higher, and JTS v.1.8 or higher). Also remember thatHibernate Spatial requires a java 1.5+ runtime. We willalso assume that you have a working Postgis databaseready.</p></section><section name="Creating a Spatially-Enabled EventManager"><p>We will create a small application to store and retrieve events we want to attend. (This isthe same use case as in the Hibernate Tutorial).</p><p>We first set up our development directory and put all the libraries in the <code>./lib</code> directory. Thisis the jts-1.8.jar, hibernate3.jar and the Hibernate dependencies (the rest). Our development directorynow looks like this:<source>.+libantlr-2.7.6.jarcommons-collections-2.1.1.jarjta.jarasm-attrs.jarcommons-logging-1.0.4.jarjts-1.8.jarasm.jardom4j-1.6.1.jarlog4j-1.2.11.jarcglib-2.1.3.jarhibernate3.jar</source>Now we need to add the Hibernate Spatial libraries to the <code>./lib</code> directory. Specifically, we need thehibernate-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:<source>.+libantlr-2.7.6.jar...hibernate3.jarhibernate-spatial-postgis-1.0-20070920.111959-1.jarhibernate-spatial-1.0-20070920.111959-1.jar</source></p><p>Finally, we also need a recent postgresql JDBC Driver together with its postgisextensions. These can be found <a href="http://jdbc.postgresql.org/download.html#current">here</a> and<a href="http://www.postgis.org/download/">here</a>, resp.<source>.+libantlr-2.7.6.jar...hibernate3.jarhibernate-spatial-postgis-1.0-20070920.111959-1.jarhibernate-spatial-1.0-20070920.111959-1.jarpostgis.jarpostgresql-8.2-506.jdbc3.jar</source></p><subsection name="The Event Class"><p>Our persistent class is the <code>Event</code> class. Since this classcontains a geometry-valued property (a property of type Geometry), its instancesare geographic objects, or features.<source>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;}}</source>We put this file in the <code>./src</code> directory of the development directory. So now we have<source>.+lib+src+eventEvent.java</source></p></subsection><subsection name="The Mapping file"><p>To map this to the class, we create the following Hibernate Mapping file.<source><![CDATA[<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>]]></source>Hibernate Spatial provides the <code>GeometryUserType</code> that enables Hibernate tostore the <code>location</code> property properly.</p><p>We save this mapping file as <code>Event.hbm.xml</code> along-side the java source file. So now we have<source>.+lib+src+eventEvent.javaEvent.hbm.xml</source></p></subsection><subsection name="The Hibernate Configuration"><p>We proceed with the hibernate configuration file. The only differencew.r.t. normal Hibernate configurations files is with the dialectproperty. Hibernate Spatial extends the Hibernate <code>Dialect</code>s so that the spatialfeatures of the database are available within HQL and the<code>SpatialCriteria</code> (see below). So instead of usingthe (in our case) PostgreSQLDialect, we use Hibernate Spatial'sextension of that dialect which is the PostGISDialect.Our <code>hibernate.cfg.xml</code> looks like this:<source><![CDATA[<?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>]]></source>As usual, we store this in the <code>./src</code> directory.</p><p>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 thesevalues depending on your set-up.</p><p>Also notice that the "hbm2dll.auto" property is activated. This will re-create thedatabase everytime the application is run (more precisely when the Hibernate SessionFactory is run).</p></subsection></section><subsection name="Building with Ant"><p>To ensure we can quickly build and test and test the application we willl use the Ant build system.You can learn about Ant <a href="http://ant.apache.org/">here</a>. We can use the exact same Ant buildfile as is used in the Hibernate Tutorial.<source><![CDATA[<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>]]></source>This build file should be saved as <code>build.xml</code> 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 <code>./bin</code> directory,together with the Hibernate configuration and mapping files.</p></subsection><subsection name="The HibernateUtil helper">The <code>HibernatUtil</code> class creates the Hibernate SessionFactory for the application, and provides a getterto it. (The code below is copied from the Hibernate Tutorial without change).<source>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.xmlsessionFactory = new Configuration().configure().buildSessionFactory();} catch (Throwable ex) {// Make sure you log the exception, as it might be swallowedSystem.err.println("Initial SessionFactory creation failed." + ex);throw new ExceptionInInitializerError(ex);}}public static SessionFactory getSessionFactory() {return sessionFactory;}}</source></subsection><subsection name="The EventManager"><p>We are now ready to write a first version of the main application class <code>EventManager</code>.<source>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 pointWKTReader 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();}}</source>We modified the <code>EventManger</code> implementation of the Hibernate Tutorial so that it storesa point for the event. The point is given as a String in the Well-Known Text (WKT) format. See<a href="http://www.vividsolutions.com/jts/javadoc/com/vividsolutions/jts/io/WKTReader.html">the JTSWTKReader JavaDoc</a> for more information about the WTK format.</p><p>The development directory now looks like this.<source>.build.xml+lib...libraries...+bin... compiled classes ...+srchibernate.cfg.xml+eventEvent.javaEvent.hbm.xmlEventManager.java+utilHibernateUtil.java</source></p><p>To easily run the EventManager we add a callable target to the Ant build file.<source><![CDATA[<target name="run" depends="compile"><java fork="true" classname="events.EventManager" classpathref="libraries"><classpath path="${targetdir}"/><arg value="${action}"/><arg value="${geom}"/></java></target>]]></source>We can now run the program using Ant as follows:<source>$ ant run -Daction=store -Dgeom="POINT(10 15)"</source>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).</p>We can check the content of the events table using the astext() function thatconverts the geometries to their WKT representation.<source>events=# select title, astext(loc) from events;title | astext----------+--------------My Event | POINT(10 15)(1 row)</source></subsection><subsection name="Spatial Queries"><p>We will now modify the <code>EventManager</code> by adding an action to list all events within a certain area.This will show how to use Hibernate Spatial for spatial querying.</p><p><strong>Important: comment out the <code>hbm2ddl.auto</code> property in the <code>hibernate.cfg.xml</code> so thatthe event table is not deleted each time we run the EventManager.</strong></p><p>Here is the addition to the <code>main</code> method in the <code>EventManager</code> thatimplements the "find" action.<source><![CDATA[...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++ ){System.out.println("Event: " + event.getTitle() +", Time: " + event.getDate() +", Location: " + event.getLocation());}}...]]></source>We also need to implement the <code>find(String filter)</code> method. </p><p>The <code>find</code> method takes a WKT string that represents a polygon, andsearches the events table for all events that are located within this polygon.</p><p>Here is the code.<source><![CDATA[...//New importsimport 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", filter, filter));List results = testCriteria.list();session.getTransaction().commit();return results;}...]]></source></p><p>The <code>SpatialRestrictions</code> class provides utility methods tocreate <code>Criterion</code> instances for spatial queries. In ourexample the <code>WITHIN</code> spatial operator is used to queryall events with a location within a specified filter geometry.</p><p>The <code>witin</code> method (as most other methods of <code>SpatialRestrictions</code>) takesthree parameters. The first is the name of the Geometry-valued property on which the filteris 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 setto <code>null</code>. If you use it, it will make the query much faster if you have a spatial index and the database expectsa specific index-based filter expression (which is the case for PostGIS, but not Oracle). (Don't forget that in this tutorialwe haven't actually created a spatial index!)</p><p>If now run:<source>$ ant run -Daction=find -Dgeom="POLYGON((1 1,20 1,20 20, 1 20, 1 1))"</source>We get<source><![CDATA[...[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)]]></source>The first line is the Filter that is echoed to <code>System.out</code>, thesecond line is the SQL generated by Hibernate (with the help of Hibernate Spatial).This is shown because the <code>show_sql</code> property is set in the configuration file.The last line is the output generated by the <code>find</code> action.</p><p>If you examine the generated SQL, you see how the <code>SpatialRestrictions.within</code>generates the fragment <code><![CDATA[(this_.LOC && ? AND within(this_.LOC, ?))]]></code>.The first part is the Postgis "Overlaps" operator that is used for the first stage filter,the second part is the precise <code>within</code> function as defined by the OGC Simple FeaturesSpecification.</p><p>An alternative approach is using HQL. The Postgis Dialect registers functions andoperators specified in the the Simple Features for SQL specification. So we could write.<source>...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();...</source>This is pretty much like any other HQL query in Hibernate, with the (slightly annoying)complication of having to create a <code>Type</code> instance forthe <code>GeometryUserType</code>.</p></subsection><section name="But I use Oracle, so what do I do?"><p>Suppose we want to implement the EventManager on top of Oracle, how do we proceed? Weneed to change the following items<ol><li>Replace the <code>hibernate-spatial-postgis-*.jar</code> provider with the<code>hibernate-spatial-oracle-*.jar</code> provider.</li><li>Replace the Postgresql Driver and the postgis.jar with the Oracle JDBC Driverand the sdoapi.jar (to be found in the java lib directory of your Oracle Database).</li><li>Modify the <code>hibernate.cfg.xml</code> configuration file so that itpoints to a suitable Oracle database, uses the Oracle JDBC driver, and has<code>OracleSpatial10gDialect</code> as dialect.</li></ol>... and your done!</p></section></body></document>