1. 功能阐述
PostGIS是Postgresql数据库的空间数据库插件,可以在Postgresql中支持空间几何(Geometry)对象的高效存储和索引。在SpringBoot项目中,MyBatisPlus是国内流行的ORP框架,然而Geometry不是原生的JDBC数据类型,要在MyBatisPlus中支持Geometry,需要做一些扩展性的开发。
2. 引入Maven依赖
引入下面三个依赖,依赖的功能如注释所示:
<!--连接Postgresql数据库-->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!--PostGIS的JDBC扩展,以支持空间数据类型-->
<dependency>
<groupId>net.postgis</groupId>
<artifactId>postgis-jdbc</artifactId>
<version>2.5.0</version>
</dependency>
<!--Geometry工具库-->
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
<version>1.17.0</version>
</dependency>
3. application.properties文件配置
注意url中的db_name换成自己的库名。另外,为了让@Select注解方式查询时自动类型转换生效,增加转换类所在包路径的配置。
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/db_name
spring.datasource.username=postgres
spring.datasource.password=postgres
mybatis-plus.type-handlers-package=com.spring.accumulator.entity.handler
4. Entity编写
StationPO表示一个基站,基站包括id和空间位置location,为了让MyBatisPlus支持Point类型,需要实现一个PointTypeHandler类型转换器,并通过@TableField的typeHandler指定。
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.spring.accumulator.entity.handler.PointTypeHandler;
import lombok.Data;
import org.locationtech.jts.geom.Point;
/**
* 基站PO
*
* @author wangrubin
* @date 2022-08-16
*/
@Data
@TableName(value = "t_base_station", autoResultMap = true)
public class StationPO {
/**
* 基站ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 基站位置
*/
@JsonIgnore
@TableField(typeHandler = PointTypeHandler.class)
private Point location;
}
5. 实现几何(Geometry)类型转换器
在JTS工具包中,Geometry是所有几何类型的父类,其具体的子类有Point(点)、LineString(线)、Polygon(面)、MultiPoint(多点)、MultiLineString(多线)、MultiPolygon(多面)。
我们基于父类Geometry实现MyBatis的类型转换器(BaseTypeHandler),自己继承即可。
5.1 编写父类转换器逻辑
写入逻辑:用WKTWriter将JTS中定义的Geometry对象转成WKT(Well-known Text)字符串;用postgis-jdbc包提供的PGGeometry对WKT进行编码,并指定数据库中几何列的空间坐标系(SRID); 将GPGeometry写入PreparedStatement。
注意:StationPO的建表语句如下:坐标系是4326,所以代码中也必须用4326。
CREATE TABLE public.t_base_station
(
id bigserial primary key,
location geometry(Point,4326)
)
读取逻辑:从postgresql中读取查询到的Geometry(字符串格式);用postgis-jdbc包提供的PGGeometry解码字符串成WKT格式的Geometry;用WKTReader将WKT转成JTS包中定义的Geometry对象。
import lombok.SneakyThrows;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.postgis.PGgeometry;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Geometry字段和MyBatis类型转换器
*
* @param <T> 具体的几何类型
* @author wangrubin
* @date 2022-08-06
*/
public abstract class AbstractGeometryTypeHandler<T extends Geometry> extends BaseTypeHandler<T> {
/**
* WKTReader非线程安全
*/
private static final ThreadLocal<WKTReader> READER_POOL = ThreadLocal.withInitial(WKTReader::new);
/**
* WKTWriter非线程安全
*/
private static final ThreadLocal<WKTWriter> WRITER_POOL = ThreadLocal.withInitial(WKTWriter::new);
/**
* 与数据库中几何列的空间坐标系保持一致,要不然写入会报错
*/
private static final int SRID_IN_DB = 4326;
@Override
public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
PGgeometry pGgeometry = new PGgeometry(WRITER_POOL.get().write(parameter));
org.postgis.Geometry geometry = pGgeometry.getGeometry();
geometry.setSrid(SRID_IN_DB);
ps.setObject(i, pGgeometry);
}
@SneakyThrows
@Override
public T getNullableResult(ResultSet rs, String columnName) {
PGgeometry pgGeometry = (PGgeometry) rs.getObject(columnName);
return getResult(pgGeometry);
}
@SneakyThrows
@Override
public T getNullableResult(ResultSet rs, int columnIndex) {
PGgeometry pgGeometry = (PGgeometry) rs.getObject(columnIndex);
return getResult(pgGeometry);
}
@SneakyThrows
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) {
PGgeometry string = (PGgeometry) cs.getObject(columnIndex);
return getResult(string);
}
private T getResult(PGgeometry pGgeometry) {
if (pGgeometry == null) {
return null;
}
String pgWkt = pGgeometry.toString();
String target = String.format("SRID=%s;", SRID_IN_DB);
String wkt = pgWkt.replace(target, "");
try {
return (T) READER_POOL.get().read(wkt);
} catch (Exception e) {
throw new RuntimeException("解析wkt失败:" + wkt, e);
}
}
}
5.2 实现子类转换器逻辑
子类转换器只需继承父类转换器,并用@MappedTypes指定子类信息即可。此处以Point(空间点)为例。
import org.apache.ibatis.type.MappedTypes;
import org.locationtech.jts.geom.Point;
/**
* Point的类型转换器
*
* @author wangrubin1
* @date 2022-08-19
*/
@MappedTypes({Point.class})
public class PointTypeHandler extends AbstractGeometryTypeHandler<Point> {
}
6. 测试
编写StationMapper类
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.spring.accumulator.entity.StationPO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface StationMapper extends BaseMapper<StationPO> {
}
编写测试类
@SpringBootTest(classes = DemoApplication.class)
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
class GeometryTests {
@Autowired
private StationMapper stationMapper;
@Test
public void insert() {
GeometryFactory factory = new GeometryFactory();
StationPO stationPo = new StationPO();
stationPo.setLocation(factory.createPoint(new Coordinate(115.3, 24.7)));
stationMapper.insert(stationPo);
}
@Test
public void select() {
StationPO stationPo = stationMapper.selectById(1L);
System.out.println(stationPo);
}
}
控制台输出:
StationPO(id=1, location=POINT (115.3 24.7))
参考文献
[1] https://blog.csdn.net/yelangkingwuzuhu/article/details/126364080