Hibernate Spatial y Postgis

De ChuWiki

Partimos de una base de datos PostgreSQL con la extensión Postgis instalada y funcionando. Veamos como usar Hibernate para acceder a los tipos de datos Geometry que la extensión Postgis añade a PostgreSQL. Aquí tienes el ejemplo completo de Hibernate y Postgis

Dependencias[editar]

El proyecto del ejemplo está en formato gradle, las dependencias necesarias son las siguientes

dependencies {
   compile group: 'org.hibernate', name: 'hibernate-core', version: '5.3.0.CR1'
   compile group: 'org.hibernate', name: 'hibernate-spatial', version: '5.3.0.CR1'
   compile group: 'org.postgresql', name: 'postgresql', version: '42.2.2'
   compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
}

Hibernate-core, última versión disponible, Hibernate-spatial compatible con la versión anterior, el Driver de postgresql y algún sistema de log para poder ver lo que Hibernate saca por el log. Puesto que hibernate usa slf4j, uno de los más sencillos y potentes para añadir el logback-classic.

Base de datos en PostgreSQL[editar]

En nuestro servidor de base de datos, para este ejemplo, creamos una base de datos postgis_test con usuario de acceso postgres y password postgres. En esa base de datos añadimos la extensión de postgis por medio del comando SQL

create extension postgis;

Las tablas dejaremos que Hibernate las cree.

Fichero de configuración de Hibernate[editar]

El fichero de configuración hibernate.cfg.xml para hibernate se puede parecer al siguiente

<!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/postgis_test</property>
      <property name="connection.username">postgres</property>
      <property name="connection.password">postgres</property>

      <!-- SQL dialect -->
      <property name="dialect">org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect</property>

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

      <mapping class="com.chuidiang.examples.hibernatepostgis.TheData" />
   </session-factory>
</hibernate-configuration>

La paramétrica de conexión a la base de datos no tiene ningún truco especial. El driver a usar es el de PostgreSQL.

El dialecto sí tiene truco. Hay que usar un dialecto de Postgis. Existe la clase org.hibernate.spatial.dialect.postgis.PostgisDialect, pero la misma clase dice que está obsoleta y que usemos en su lugar alguno de los dialectos concretos. Mi base de datos PostgreSQL es versión 9.6, así que uso la de PostgisPG95Dialect que es la más cercana, puesto que no hay 96.

En la propiedad hbm2dll.auto le indicamos que cree las tablas, así nos evitamos la complicación y podemos ver lo que hace.

En la última línea indicamos la clase que vamos a hacer persistente en base de datos, con sus anotaciones de Hibernate correspondientes.

La clase a guardar[editar]

La clase para guardar en base de datos y para la que Hibernate creará una tabla, es sencilla, sólo un id y un Geometry

package com.chuidiang.examples.hibernatepostgis;

import com.vividsolutions.jts.geom.Geometry;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class TheData {
    @Id
    private long id;

    private Geometry theGeometry;

    // Setter y Getter ...

    @Override
    public String toString() {
        return "TheData{" +
                "id=" + id +
                ", theGeometry=" + theGeometry +
                '}';
    }
}

Geometry es una clase de la librería jts.jar que viene como dependencia heredada de hibernate-spatial. Es la que usaremos para meter las geometrías en base de datos.

Conexión con Hibernate[editar]

El arranque de hibernate no tiene nada especial, de la forma habitual que lee el fichero hibernate.cfg.xml que encuentre en el classpath (fichero en src/main/resources en un proyecto gradle o maven).

public class Main {
    public static void main(String[] args) {

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

        ...

La Tabla en Base de Datos[editar]

Una vez arranquemos nuestro programa, Hibernate creará la tabla de base de datos. Esto es lo que va a crear

CREATE TABLE public.thedata
(
  id bigint NOT NULL,
  thegeometry geometry,
  CONSTRAINT thedata_pkey PRIMARY KEY (id)
)

insert Geometry[editar]

El siguiente código hace un insert en base de datos usando Hibernate

private static GeometryFactory geometryFactory = new GeometryFactory();

...

    private static void insertGeometry(SessionFactory sessionFactory) {
        Session session = sessionFactory.openSession();
        session.beginTransaction();

        TheData data = new TheData();
        data.setId(1L);
        LineString geometry = geometryFactory.createLineString(new Coordinate[]{new Coordinate(1,2), new Coordinate(3,4)});
        data.setTheGeometry(geometry);

        session.save(data);
        session.getTransaction().commit();
        session.close();
    }

Empezamos la transacción de Hibernate. Creamos una instancia de TheData y la rellenamos. Ponemos nosotros mismos el id a 1 y con GeometryFactory creamos un LineString con un par de puntos. Un simple session.save(data) y listo, ya está la inserción en base de datos

Query Geometry[editar]

El código java para la consulta puede ser así

    private static void queryGeometry(SessionFactory sessionFactory) {
        Session session = sessionFactory.openSession();

        session.beginTransaction();

        List<TheData> list = session.createQuery("from TheData").list();

        if (list.size()==0){
            System.out.println("There are no entities");
        } else {
            for (TheData data : list) {
                System.out.println(data);
            }
        }

        session.getTransaction().commit();
        session.close();
    }

Se empieza la transacción (no es necesario para una consulta, pero bueno), se lanza la consulta "from TheData" y se recoge el resultado como lista. Un bucle para recorrer los resultados da lugar a esta salida por pantalla

TheData{id=1, theGeometry=LINESTRING (1 2, 3 4)}

Update Geometry[editar]

Para modificar, el siguiente código

    private static void updateGeometry(SessionFactory sessionFactory) {
        Session session = sessionFactory.openSession();

        session.beginTransaction();

        TheData theData = session.get(TheData.class, 1L);

        LineString geometry = geometryFactory.createLineString(new Coordinate[]{
                new Coordinate(5,6),
                new Coordinate(7,8),
                new Coordinate(9,10)});
        theData.setTheGeometry(geometry);

        session.saveOrUpdate(theData);
        session.getTransaction().commit();
        session.close();
    }

Se hace una consulta por id=1 para obtener el dato, se crea un nuevo LineString con tres puntos, se mete dentro de thedata y basta un salvar para guardar la modificación en base de datos.

Delete Geometry[editar]

El borrado no tiene nada especial, como el borrado de cualquier otra entidad de java en Hibernate

    private static void deleteGeometry(SessionFactory sessionFactory) {
        Session session = sessionFactory.openSession();

        session.beginTransaction();

        Query query = session.createQuery("delete from TheData where id=:id");
        query.setParameter("id",1L);
        query.executeUpdate();

        session.getTransaction().commit();
        session.close();
    }

No usamos session.delete() porque requiere consultar previamente el objeto para pasarlo como parámetro. Así que usamos una Query con "delete from ..."