Rev 312 | Blame | Compare with Previous | Last modification | View Log | RSS feed
<?xml version="1.0" encoding="UTF-8"?><document xmlns="http://maven.apache.org/XDOC/2.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd"><properties><title>Tutorial</title><author>Karel Maesen</author></properties><body><section name="Introduction"><p>This tutorial gives a quick overview of how to getHibernate Spatial 1.x 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://docs.jboss.org/hibernate/core/3.6/quickstart/en-US/html/">Hibernate Tutorial</a>and uses the same example. If youhaven't read the Hibernate Tutorial, and are new to Hibernate, then<strong>please read it before proceeding here</strong>.</p><p>The Hibernate Tutorial uses the H2 in-memorydatabase. Although Hibernate Spatial supports GeoDB, the spatial extension of H2, we require a postgis databasefor this tutorial. For information on how to create a postgis database, you should consult<a href="http://postgis.refractions.net/docs/">the postgis documentation</a>.</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><subsection name="Setup"><p>We first need to set up our development environment. We will use the<a href="http://maven.org/">Maven</a>build tool in this tutorial (as this is also used in the Hibernate Tutorial).</p><p>Maven can generate the basic structure of our simple application using the<code>mvnarchetype:generate</code>.For this example, we specify type (the default) and set<code>groupId</code>to <code>org.hibernatespatial.tutorials</code>,<code>artifactId</code>to<code>event-tutorial</code>and<code>package</code>to<code>event</code>.</p><source>mvn archetype:generate[INFO] Scanning for projects...[INFO] Searching repository for plugin with prefix: 'archetype'.[INFO] ------------------------------------------------------------------------[INFO] Building Maven Default Project[INFO] task-segment: [archetype:generate] (aggregator-style)[INFO] ------------------------------------------------------------------------[INFO] Preparing archetype:generate[INFO] No goals needed for project - skipping[INFO] Setting property: classpath.resource.loader.class =>'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.[INFO] Setting property: velocimacro.messages.on => 'false'.[INFO] Setting property: resource.loader => 'classpath'.[INFO] Setting property: resource.manager.logwhenfound => 'false'.[INFO] [archetype:generate {execution: default-cli}]Choose archetype:1: internal -> appfuse-basic-jsf (AppFuse archetype for creating a web application with Hibernate,......36: internal -> wicket-archetype-quickstart (A simple Apache Wicket project)Choose a number:(1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36)15: :......Define value for groupId: : org.hibernatespatial.tutorialsDefine value for artifactId: : event-tutorialDefine value for version: 1.0-SNAPSHOT: :Define value for package: : eventConfirm properties configuration:groupId: org.hibernatespatial.tutorialsartifactId: event-tutorialversion: 1.0-SNAPSHOTpackage: eventY: : Y......http://repo1.maven.org/maven2/org/apache/maven/archetypes/maven-archetype-quickstart/1.0/maven-archetype-quickstart-1.0.jar[INFO] ----------------------------------------------------------------------------[INFO] Using following parameters for creating OldArchetype: maven-archetype-quickstart:1.0[INFO] ----------------------------------------------------------------------------[INFO] Parameter: groupId, Value: org.hibernatespatial.tutorials[INFO] Parameter: packageName, Value: event[INFO] Parameter: package, Value: event[INFO] Parameter: artifactId, Value: event-tutorial[INFO] Parameter: basedir, Value: /home/test[INFO] Parameter: version, Value: 1.0-SNAPSHOT[INFO] ********************* End of debug info from resources from generated POM***********************[INFO] OldArchetype created in dir: /home/test/event-tutorial[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESSFUL[INFO] ------------------------------------------------------------------------[INFO] Total time: 36 seconds[INFO] Finished at: Mon Apr 05 17:23:22 CEST 2010[INFO] Final Memory: 15M/158M[INFO] ------------------------------------------------------------------------</source><p>This results in the following directory structure.</p><source><![CDATA[.|-- pom.xml`-- src|-- main| |-- java| | |-- event| | `-- App.java`-- test`-- java`-- event]]></source><p>We now need to edit the pom to add the required dependencies and repositories (see also the <ahref="mavenquick.html">Maven Quick Start</a>.). The versions of the Postgisand Postgresql drivers don't matter too much. This tutorial should work with any version of the Postgisdriver later than 1.3.3, and any Postgresql JDBC driver that agrees with your database.</p><source><![CDATA[<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>org.hibernatespatial.tutorials</groupId><artifactId>event-tutorial</artifactId><packaging>jar</packaging><version>1.0-SNAPSHOT</version><name>event-tutorial</name><url>http://maven.apache.org</url><build><!-- we dont want the version to be part of the generated war file name --><finalName>${artifactId}</finalName><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.5</source><target>1.5</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build><dependencies><!-- Hibernate Spatial for postgis. This will include Hibernate Spatial Core and JTS --><dependency><groupId>org.hibernatespatial</groupId><artifactId>hibernate-spatial-postgis</artifactId><version>1.1</version></dependency><!-- the Postgis JDBC driver --><dependency><groupId>org.postgis</groupId><artifactId>postgis-jdbc</artifactId><version>1.3.3</version></dependency><!-- the postgresql driver --><dependency><groupId>postgresql</groupId><artifactId>postgresql</artifactId><version>8.4-701.jdbc3</version></dependency><!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend --><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.5.11</version></dependency></dependencies><!-- add repositories for JTS and Hibernate Spatial and Hibernate (JBoss) --><repositories><repository><id>OSGEO GeoTools repo</id><url>http://download.osgeo.org/webdav/geotools</url></repository><repository><id>Hibernate Spatial repo</id><url>http://www.hibernatespatial.org/repository</url></repository><!-- add JBOSS repository for easy access to Hibernate libraries --><repository><id>JBOSS</id><url>https://repository.jboss.org/nexus/content/repositories/releases/</url></repository></repositories></project>]]></source><p>With the command<code>mvn dependency:list</code>we can see which libraries are required for our minimalHibernate Spatial application.</p><source>mvn dependency:list....[INFO] antlr:antlr:jar:2.7.6:compile[INFO] com.vividsolutions:jts:jar:1.11:compile[INFO] commons-collections:commons-collections:jar:3.1:compile[INFO] dom4j:dom4j:jar:1.6.1:compile[INFO] javassist:javassist:jar:3.11.0.GA:compile[INFO] javax.transaction:jta:jar:1.1:compile[INFO] org.hibernate:hibernate-commons-annotations:jar:3.2.0.Final:compile[INFO] org.hibernate:hibernate-core:jar:3.6.0.Final:compile[INFO] org.hibernate.javax.persistence:hibernate-jpa-2.0-api:jar:1.0.0.Final:compile[INFO] org.hibernatespatial:hibernate-spatial:jar:1.1:compile[INFO] org.hibernatespatial:hibernate-spatial-postgis:jar:1.1:compile[INFO] org.postgis:postgis-jdbc:jar:1.3.3:compile[INFO] org.postgis:postgis-stubs:jar:1.3.3:compile[INFO] org.slf4j:slf4j-api:jar:1.5.11:compile[INFO] org.slf4j:slf4j-simple:jar:1.5.11:compile[INFO] postgresql:postgresql:jar:8.4-701.jdbc3:compile[INFO] xerces:xercesImpl:jar:2.4.0:compile</source><p>Hibernate Spatial works with a wide range of versions of these libraries, so don't be too concernedif you seeeither more recent or slightly older versions.</p></subsection><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.</p><source><![CDATA[package event;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><p>We put this file in the<code>./src/main/java/event</code>directory of the development directory.</p></subsection><subsection name="The Mapping file"><p>To map this class to the database table, we create the following Hibernate Mapping file:</p><source><![CDATA[<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping package="event"><class name="Event" table="EVENTS"><id name="id" column="EVENT_ID"><generator class="native"/></id><property name="date" type="timestamp" column="EVENT_DATE"/><property name="title" type="string"/><property name="location" type="org.hibernatespatial.GeometryUserType" column="LOC"/></class></hibernate-mapping>]]></source><p>Hibernate Spatial provides the<code>GeometryUserType</code>that enables Hibernate to store the<code>location</code>property properly.</p><p>We save this mapping file in<code>./src/main/resources/event/Event.hbm.xml</code>along-side the java source file.</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 in 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://www.hibernate.org/dtd/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="event/Event.hbm.xml"/></session-factory></hibernate-configuration>]]></source>As is usual when building with maven, we store this file in the<code>./src/main/resources</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 used, 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><section name="The HibernateUtil helper"><p>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).</p><source><![CDATA[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></section><section name="The EventManager"><p>We are now ready to write a first version of the main application class<code>EventManager</code>.</p><source><![CDATA[package event;import org.hibernate.Session;import java.util.Date;import com.vividsolutions.jts.geom.Point;import com.vividsolutions.jts.geom.Geometry;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(), assemble(args));}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();}/*** Utility method to assemble all arguments save the first into a String*/private static String assemble(String[] args){StringBuilder builder = new StringBuilder();for(int i = 1; i<args.length;i++){builder.append(args[i]).append(" ");}return builder.toString();}}]]></source><p>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.(The <code>assemble()</code> method is necessary because the Maven exec pluginparses string with spaces as a list of command-line arguments.)</p><p>The development directory now looks like this.<source><![CDATA[.|-- pom.xml`-- src|-- main| |-- java| | |-- event| | | |-- Event.java| | | `-- EventManager.java| | `-- util| | `-- HibernateUtil.java| `-- resources| |-- event| | `-- Event.hbm.xml| `-- hibernate.cfg.xml`-- test`-- java`-- event]]></source></p><p>We can now build this program using maven:<source><![CDATA[ $ mvn compile ]]></source>We can now execute the program using maven as follows:<source><![CDATA[ mvn exec:java -Dexec.mainClass="event.EventManager" -Dexec.args="store POINT(10 5)"]]></source></p><p>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><p>We can check the content of the events table using the astext() function thatconverts the geometries to their WKT representation.</p><source><![CDATA[ events=# select title, astext(loc) from events;title | astext----------+--------------My Event | POINT(10 15)(1 row) ]]></source></section><section 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(), assemble(args));}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());}}...]]></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));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>) takestwo parameters. The first is the name of the Geometry-valued property on which the filteris applied; the second is the filter geometry.</p><p>If now run:<source><![CDATA[ $ mvn exec:java -Dexec.mainClass="event.EventManager" -Dexec.args="find POLYGON((1\ 1,20\ 1,20\20,1\ 20,1\ 1))" ]]></source>We get<source><![CDATA[...Filter is : POLYGON ((1 1, 20 1, 20 20, 1 20, 1 1))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, ?))Event: My Event, Time: 2010-04-05 19:58:36.896, Location: POINT (10 5)...]]></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>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 = GeometryUserType.TYPE;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 explicitly specify the<code>Type</code>for the<code>GeometryUserType</code>in the<code>setParameter</code>-method.</p></section><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</p><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 with the Oracle JDBC Driver.</li><li>Remove the<code>postgis.jar</code></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><p>... and your done!</p></section><section name=".. and what about MySQL?"><p>Recent versions of MySQL have (limited) support for spatial types. To getHibernate Spatial to work with MySQL, change the following items:</p><ol><li>Replace the<code>hibernate-spatial-postgis-*.jar</code>provider with the<code>hibernate-spatial-mysql-*.jar</code>provider.</li><li>Replace the Postgresql Driver with the MySQL JDBC Driver.</li><li>Remove the<code>postgis.jar</code></li><li>Modify the<code>hibernate.cfg.xml</code>configuration file so that itpoints to a suitable MySQL database, uses the MySQL JDBC driver, and has<code>MySQLSpatialDialect</code>as dialect.</li></ol><p>Please note that MySQL's implementation of the spatial types is incomplete at this time. Certainfunctions of Hibernate Spatial will fail at this time.</p><p>The MySQLSpatialDialect extends the vanilla MySQLDialect and therefore only supports the MyISAM tables.</p></section><section name=".. and what about Microsoft SQL Server 2008?"><p>Starting with SQL Server 2008, Microsoft has added full support for OGC Simple Features for SQL. To usethis database with Hibernate Spatial, change the following items:</p><ol><li>Replace the<code>hibernate-spatial-postgis-*.jar</code>provider with the<code>hibernate-spatial-sqlserver-*.jar</code>provider.</li><li>Replace the Postgresql Driver with the Microsoft SQL Server JDBC Driver.</li><li>Remove the<code>postgis.jar</code></li><li>Modify the<code>hibernate.cfg.xml</code>configuration file so that itpoints to a suitable SQL Server database, uses the SQL Server JDBC driver, and has<code>SQLServerSpatialDialect</code>as dialect.</li></ol></section><section name="Can I Use JPA Annotations with Hibernate Spatial?"><p>In stead of using<code>*.hbm.xml</code>mapping files, you can also useJPA annotations. Well almost: JPA doesn't support spatial objects, and doesn'thave 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<code>UserType</code>s.</p><p>Let's modify the code to use annotations. We change the source code of the Events class so that it looks like this</p><source><![CDATA[package event;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;}}]]></source><p>Note that for the Geometry field we neededto use the Hibernate-specific<code>@Type</code>annotation.</p><p>Next, we replace the mapping resource by replacingthe following line from the<code>hibernate.cfg.xml</code></p><source><mapping resource="event/Event.hbm.xml"/></source><p>with a declaration of mapped classes.</p><source><mapping class="event.Event"></source><p>Finally, we change the<code>HibernateUtil</code>so it usesan AnnotationConfiguration object. Change the line:</p><source>...sessionFactory = new Configuration().configure().buildSessionFactory();...</source><p>needs to change to</p><source>...sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();...</source><p>Now the operations should work as before.</p></section></body></document>