前言
最近在做一个遗留系统的改造,感觉相当头疼。原本分离的空间库和关系库,在本次改造中需要合并,这也就意味着要把一些空间信息合并到关系表中,本来考虑到使用的postgresql数据库以及之前使用mybatis的经验,以为在hibernate中处理空间字段是一件很简单的事,但真正做起来却发现很是麻烦。下面就简单记录一下相关信息。
原因分析
主要原因可以梳理如下:
- 由于与半自动框架mybatis不一样,hibernate是一套算自动的框架,他需要知道写入数据以及查询结果的具体的数据类型,因此在配置上需要特别处理。
- 由于遗留系统的历史也特别久远,可以追溯到2012年,原先采用的各种依赖都已经过时,所以在添加新的依赖时会有严重的兼容问题。
- 由于早在2012年团队开发技术并不成熟,采用的方法现在看来十分奇葩,正应了那句话“我也不知道怎么回事,反正不改就没问题,但改了就挂了”。
- 由于hibernate也在不断演进,而且大版本并不兼容,所以自身会产生很多兼容问题。
不同Dao框架空间字段的兼容方式
本节介绍一下各种Dao框架的空间信息兼容方式。
Hibernate4 兼容空间数据的方式
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兼容空间数据的方式
有效参考链接
你需要的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>
相信有了上面三个配置的提示,有一定经验的开发人员已经能够解决这个问题了
需要注意的点
- 千万不要引入其他的hibernate依赖,仔细检查POM文件,可能导致各种sessionFactory初始化bug
- 由于空间数据库需要用到postgis的方言,所以org.hibernatespatial.postgis.PostgisDialect这个要注意
- 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>
遇到的坑和心得
- 历史久远的服务往往意味着技术栈不一致,改造前要有心理准备
- 遗留系统的依赖兼容性往往较低,添加新依赖要小心和逐步尝试。
- 面对遗留系统不能上来就是干,全面彻底的分析是磨刀不误砍柴工工。
总结
出来混早晚都要还。