Hibernate与空间数据操作的那些事

187 阅读3分钟

前言

最近在做一个遗留系统的改造,感觉相当头疼。原本分离的空间库和关系库,在本次改造中需要合并,这也就意味着要把一些空间信息合并到关系表中,本来考虑到使用的postgresql数据库以及之前使用mybatis的经验,以为在hibernate中处理空间字段是一件很简单的事,但真正做起来却发现很是麻烦。下面就简单记录一下相关信息。

原因分析

主要原因可以梳理如下:

  • 由于与半自动框架mybatis不一样,hibernate是一套算自动的框架,他需要知道写入数据以及查询结果的具体的数据类型,因此在配置上需要特别处理。
  • 由于遗留系统的历史也特别久远,可以追溯到2012年,原先采用的各种依赖都已经过时,所以在添加新的依赖时会有严重的兼容问题。
  • 由于早在2012年团队开发技术并不成熟,采用的方法现在看来十分奇葩,正应了那句话“我也不知道怎么回事,反正不改就没问题,但改了就挂了”。
  • 由于hibernate也在不断演进,而且大版本并不兼容,所以自身会产生很多兼容问题。

不同Dao框架空间字段的兼容方式

本节介绍一下各种Dao框架的空间信息兼容方式。

Hibernate4 兼容空间数据的方式

www.hibernatespatial.org/documentati…

pom依赖


 <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-spatial</artifactId>
            <version>4.0</version>
        </dependency>

Hibernate配置


<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="org.hibernate.events.jpa" transaction-type="RESOURCE_LOCAL">
       <properties>
           <property name="hibernate.dialect" value="org.hibernate.spatial.dialect.postgis.PostgisDialect"/>

           <property name="hibernate.connection.driver_class" value="org.postgresql.Driver"/>
           <property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432:hstutorial"/>
           <property name="hibernate.connection.username" value="hstutorial"/>
           <property name="hibernate.connection.password" value="hstutorial"/>
           <property name="hibernate.connection.pool_size" value="5"/>

           <property name="hibernate.show_sql" value="true"/>
           <property name="hibernate.format_sql" value="true"/>

           <property name="hibernate.max_fetch_depth" value="5"/>

           <property name="hibernate.hbm2ddl.auto" value="update"/>

       </properties>
    </persistence-unit>
</persistence>

实体类配置


<!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.hibernate.spatial.GeometryType" column="LOC"/>
</class>
</hibernate-mapping>

Hibernate3兼容空间数据的方式

有效参考链接

www.hibernatespatial.org/documentati…

你需要的maven依赖

<!-- https://mvnrepository.com/artifact/org.hibernatespatial/hibernate-spatial -->
<dependency>
    <groupId>org.hibernatespatial</groupId>
    <artifactId>hibernate-spatial</artifactId>
    <version>1.1.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.hibernatespatial/hibernate-spatial-postgis -->
<dependency>
    <groupId>org.hibernatespatial</groupId>
    <artifactId>hibernate-spatial-postgis</artifactId>
    <version>1.1</version>
</dependency>

实体类的配置文件写法


<!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>

Hibernate配置文件

<?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>   

相信有了上面三个配置的提示,有一定经验的开发人员已经能够解决这个问题了
需要注意的点

  1. 千万不要引入其他的hibernate依赖,仔细检查POM文件,可能导致各种sessionFactory初始化bug
  2. 由于空间数据库需要用到postgis的方言,所以org.hibernatespatial.postgis.PostgisDialect这个要注意
  3. postgresql本身数据库版本并不敏感

Mybatis兼容空间数据的方式

当然你在这里用Mybatis Plus也可以。注意需要在启动类上加上MapperScan。
Mapper.java

package com.zw.mapper;

import com.zw.se2.model.MapElement;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface MapEelementMapper {

   

    /**
     * @param mapElement map元素
     */
    void addMapElement(MapElement mapElement);

    /**
     * @param id id
     * @return 依据id返回数据
     */
    MapElement findById(Long id);

    

pom依赖

  <!--mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis.spring.boot</groupId>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

mapper.xml


<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zw.se2.platform.domain.dim.mapper.MapEelementMapper">
       <!--    id查询-->
    <select id="findById" parameterType="Long" resultMap="MapElementMap">
        SELECT id,
               name,
               unique_code,
               type,
               sub_type,
               remark,
               price,
               create_time,
               update_time,
               deleted,
               ST_AsGeoJson(element_location) as element_location
        FROM map_elements
        where id = #{id} and deleted = false;
    </select>

    <!--    圆形区域查询-->
    <select id="findMapElementByCircle" resultMap="MapElementMap">
        SELECT id, name, unique_code,
        type, sub_type, remark, price,
        create_time,
        update_time,
        deleted,
        ST_AsGeoJson(element_location) as element_location
        from map_elements
        where ST_DWithin(element_location :: geography, ST_GeomFromText('POINT' || #{geometry}, 4326 ) :: geography,
        #{radius} ) IS TRUE and deleted = false
        <if test="type != null">
            and type = #{type}
        </if>
        <if test="subType != null">
            and sub_type = #{subType}
        </if>
    </select>

    <insert id="addMapElement" parameterType="MapElement" useGeneratedKeys="true"
            keyProperty="id">
        insert into map_elements(id, name, unique_code, type, sub_type, remark, price, element_location, create_time,
                                 update_time, deleted)
        values (#{id}, #{name}, #{uniqueCode}, #{type}, #{subType}, #{remark}, #{price}, ST_GeomFromText('POINT' ||
                                                                                                         #{geoStr},
                                                                                                         4326),
                #{createTime}, #{updateTime}, #{deleted})
    </insert> 

</mapper>

遇到的坑和心得

  • 历史久远的服务往往意味着技术栈不一致,改造前要有心理准备
  • 遗留系统的依赖兼容性往往较低,添加新依赖要小心和逐步尝试。
  • 面对遗留系统不能上来就是干,全面彻底的分析是磨刀不误砍柴工工。

总结

出来混早晚都要还。
在这里插入图片描述