目录
-
- 一、问题回顾
- 二、解决思路
- 三、核心实现
-
- [3.1 GeometryJsonSerializer(核心)](#3.1 GeometryJsonSerializer(核心))
- [3.2 Jackson 全局配置(推荐)](#3.2 Jackson 全局配置(推荐))
- [3.3 Entity 是否需要修改?](#3.3 Entity 是否需要修改?)
- [3.4 Controller 瘦身](#3.4 Controller 瘦身)
- 四、效果展示
-
- [4.1 接口返回](#4.1 接口返回)
- [4.2 Swagger 文档效果](#4.2 Swagger 文档效果)
- [五、进阶:返回 GeoJSON(加分项)](#五、进阶:返回 GeoJSON(加分项))
-
- [5.1 GeometryGeoJsonSerializer](#5.1 GeometryGeoJsonSerializer)
- [5.2 返回效果](#5.2 返回效果)
- 六、常见坑位提醒
- 七、小结
一、问题回顾
在上一篇(基础篇)中,https://blog.csdn.net/weixin_45011889/article/details/162789976?fromshare=blogdetail\&sharetype=blogdetail\&sharerId=162789976\&sharerefer=PC\&sharesource=weixin_45011889\&sharefrom=from_link,我们已经实现了 Geometry 字段的增删改查,TypeHandler 也能正确工作。但发现了一个问题:
Service 层会充斥着大量的 WKT 转换代码:
java
resp.setGeom(wktWriter.write(entity.getGeom()));
这带来了几个问题:
- Service 污染:Service 本应只关心业务逻辑,现在却要关心展示格式
- Swagger 不友好:Geometry 字段在 Swagger 文档中显示为 Object,前端同事无法直观理解
- 重复劳动:每个接口都要写一遍 WKT 转换
有没有更优雅的方式?
二、解决思路
核心观点:TypeHandler 负责"JTS ↔ JDBC",Jackson 负责"JTS ↔ JSON"。各司其职。
| 层级 | 职责 |
|---|---|
| TypeHandler | JTS Geometry ↔ PostGIS EWKB(JDBC 持久化) |
| Jackson Serializer | JTS Geometry → WKT / GeoJSON(JSON 展示) |
三、核心实现
3.1 GeometryJsonSerializer(核心)
新增文件:handler/GeometryJsonSerializer.java
java
package org.pgd.postgisdemo.handler;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.WKTWriter;
import java.io.IOException;
/**
* Jackson 序列化器:将 JTS Geometry 转为 WKT 字符串
*/
public class GeometryJsonSerializer extends JsonSerializer<Geometry> {
private static final WKTWriter WKT_WRITER = new WKTWriter();
@Override
public void serialize(Geometry value, JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
if (value == null) {
gen.writeNull();
return;
}
// Geometry → WKT
gen.writeString(WKT_WRITER.write(value));
}
}
关键点:只做一件事------Geometry → WKT,不依赖 MyBatis,不依赖 Spring,职责单一。
3.2 Jackson 全局配置(推荐)
新增文件:config/JacksonConfig.java
java
package org.pgd.postgisdemo.config;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.locationtech.jts.geom.Geometry;
import org.pgd.postgisdemo.handler.GeometryJsonSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public SimpleModule geometryModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(
Geometry.class,
new GeometryJsonSerializer()
);
return module;
}
}
为什么推荐全局配置?
- Entity 保持纯净,不污染领域模型
- 一处配置,全局生效
- 以后想换 GeoJSON,只改这一个类
3.3 Entity 是否需要修改?
答:不需要。 Entity 完全不用改:
java
@Data
@TableName(value = "poi", autoResultMap = true)
public class PoiEntity {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String category;
@TableField(typeHandler = GeometryTypeHandler.class)
private Geometry geom;
}
Jackson 全局配置会自动对 Geometry 类型生效,不需要任何注解。
3.4 Controller 瘦身
改造前(第一篇):
java
// Service 里要写
resp.setGeom(wktWriter.write(entity.getGeom()));
// 或者 Controller 里要写
resp.setGeom(new WKTWriter().write(entity.getGeom()));
改造后(1.1 篇):
java
@GetMapping("/detail/{id}")
public R<PoiEntity> detail(@PathVariable Long id) {
PoiEntity entity = poiService.getById(id);
return entity != null ? R.ok(entity) : R.fail("POI 不存在");
}
Controller 彻底无空间逻辑,Service 不再关心展示格式。
四、效果展示
4.1 接口返回
改造前:
json
// Service 手动转换后
"geom": "POINT(116.397 39.903)"
改造后:
json
// 直接返回 PoiEntity,Jackson 自动序列化
{
"id": 1,
"name": "天安门",
"category": "景点",
"geom": "POINT(116.397 39.903)"
}
4.2 Swagger 文档效果
json
{
"id": 1,
"name": "天安门",
"category": "景点",
"geom": "POINT(116.397 39.903)"
}
前端同事一眼看懂,联调效率大幅提升。
五、进阶:返回 GeoJSON(加分项)
如果前端使用地图组件(如 Mapbox / Leaflet / OpenLayers),GeoJSON 比 WKT 更友好。
5.1 GeometryGeoJsonSerializer
java
public class GeometryGeoJsonSerializer
extends JsonSerializer<Geometry> {
private final ObjectMapper geoJsonMapper =
new ObjectMapper(new GeoJsonObjectMapper());
@Override
public void serialize(Geometry value, JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
if (value == null) {
gen.writeNull();
return;
}
gen.writeRawValue(
geoJsonMapper.writeValueAsString(value)
);
}
}
5.2 返回效果
json
{
"id": 1,
"name": "天安门",
"category": "景点",
"geom": {
"type": "Point",
"coordinates": [116.397, 39.903]
}
}
前端地图组件可以直接消费,无需额外转换。
六、常见坑位提醒
| 坑位 | 说明 |
|---|---|
| WKTWriter 线程安全 | JTS WKTWriter 是线程安全的,可以定义为 static final |
| 重复注册 | 不要同时在 @JsonSerialize 和全局配置里注册,会冲突 |
| TypeHandler vs Jackson | TypeHandler 管持久化(JDBC),Jackson 管展示(JSON),职责不同 |
| Entity 纯洁性 | 推荐全局配置,不污染 Entity 领域模型 |
七、小结
本篇只新增了两个类:
- GeometryJsonSerializer --- 核心序列化逻辑
- JacksonConfig --- 全局注册
实现了 Geometry 字段在 Swagger 和接口返回中的优雅展示,Controller 与 Service 完全无需感知展示格式。
三句话总结:
- TypeHandler 管持久化,Jackson 管展示
- 全局配置优于注解,Entity 保持纯净
- 需要 GeoJSON 时,只换 Serializer,不动业务代码