构建电子地图网站——03 搭建开发平台:MyBatis+TypeHandler+Jts
2022-11-16 00:00:00

上一节已经搭建好了项目结构,连接好了数据库,可以通过url进行简单的增删改查操作。这节主要是针对geometry几何对象的增删改查,这个就需要mybatis自定义类型了!

我们的数据有几何类型:点和面。

  • 当我们在navicat中运行一条sql:
1
SELECT geom FROM v6_time_cnty_pts_utf_wgs84 LIMIT 1

查询的结果geom是geohash编码的:0101000020E6100000A165DD3F16C55B4089963C9E96814340

  • 再运行另一条sql:
1
sqlSELECT st_astext(geom) FROM v6_time_cnty_pts_utf_wgs84 LIMIT 1

查询结果是WKT格式的:

POINT(111.079483 39.012409)

  • WKTgeohash都是空间数据的存储格式,但是WKT比geohash编码更直观;在mybatis中,直接查geometry对象,返回的是字符类型的geohash,但是交互的时候,我们不能返一串geohash码。对于后端来说,我们应该直接操作geometry类,给前端提供json数组

1 引入依赖

Jts是空间处理jar包,功能很全很强大,这个jar包的maven依赖,我们之前已经在pom中引用了,再加一个解析json的jar包。

1
2
3
4
5
6
7
<!--引入alibaba的json处理jar包-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>

2 增加mybatis自定义类

新建一个mybatis包==》新建一个GeometryTypeHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@MappedTypes(Geometry.class)
public class GeometryTypeHandler extends BaseTypeHandler<Geometry> {

public void setNonNullParameter(PreparedStatement preparedStatement, int i, Geometry geometry, JdbcType jdbcType) throws SQLException {
PGobject pGobject=new PGobject();
pGobject.setValue(geometry.toString());
pGobject.setType("geometry");
preparedStatement.setObject(i,pGobject);
}

public Geometry getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
PGobject pGgeometry= (PGobject) resultSet.getObject(columnName);
if(pGgeometry==null){
return null;
}else{
WKBReader wkbReader=new WKBReader();
try {
return wkbReader.read(WKBReader.hexToBytes(pGgeometry.getValue()));
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}

public Geometry getNullableResult(ResultSet resultSet, int columnIndex) throws SQLException {
PGobject pGgeometry= (PGobject) resultSet.getObject(columnIndex);
if(pGgeometry==null){
return null;
}else{
WKBReader wkbReader=new WKBReader();
try {
return wkbReader.read(WKBReader.hexToBytes(pGgeometry.getValue()));
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}

public Geometry getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
PGobject pGgeometry= (PGobject) callableStatement.getObject(i);
if(pGgeometry==null){
return null;
}else{
WKBReader wkbReader=new WKBReader();
try {
return wkbReader.read(WKBReader.hexToBytes(pGgeometry.getValue()));
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
}
}

3 修改model类

添加一个geometry属性

1
2
3
4
5
6
7
8
@Getter
@Setter
@ToString
public class PointModel {
private Integer gId;
private String nameCh;
private Geometry geometry;
}

4 修改mapper

主要是加上typeHandler="com.history.gismap.mybatis.GeometryTypeHandler"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//http://mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.history.gismap.dao.MapDao" >
<resultMap id="pointModelResult" type="com.history.gismap.model.PointModel">
<result property="gId" column="gid" jdbcType="BIGINT"/>
<result property="nameCh" column="name_ch" jdbcType="VARCHAR"/>
<result property="geometry" column="geom" typeHandler="com.history.gismap.mybatis.GeometryTypeHandler"/>
</resultMap>
<sql id="BASE_TABLE">
v6_time_cnty_pts_utf_wgs84
</sql>
<sql id="BASE_COLUMN">
gid,name_ch,geom
</sql>
<select id="getCntyPoint" resultMap="pointModelResult">
SELECT
<include refid="BASE_COLUMN"></include>
FROM
<include refid="BASE_TABLE"/>
WHERE gid=#{gId}
</select>
<insert id="insertCntyPoint" parameterType="com.history.gismap.model.PointModel">
INSERT INTO
<include refid="BASE_TABLE"/>
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="gId != null">
gid,
</if>
<if test="nameCh != null">
name_ch,
</if>
<if test="geometry != null">
geom,
</if>
</trim>
<trim prefix="VALUES(" suffix=")" suffixOverrides=",">
<if test="gId != null">
#{gId, jdbcType=BIGINT},
</if>
<if test="nameCh != null">
#{nameCh, jdbcType=VARCHAR},
</if>
<if test="geometry != null">
#{geometry,typeHandler=com.history.gismap.mybatis.GeometryTypeHandler}
</if>
</trim>
</insert>
<update id="updateCntyPoint" parameterType="com.history.gismap.model.PointModel">
UPDATE
<include refid="BASE_TABLE"/>
SET
name_ch=#{nameCh},
geom=#{geometry,typeHandler=com.history.gismap.mybatis.GeometryTypeHandler}
WHERE
gid=#{gId}
</update>
<delete id="deleteCntyPoint" parameterType="com.history.gismap.model.PointModel">
DELETE FROM
<include refid="BASE_TABLE"/>
WHERE
gid=#{gId}
</delete>
</mapper>

5 修改controller

为了便于前端读写,我们要把geometry对象转化成geojson。这个改动比较大,查询返回结果、增加入参、修改入参都改成了json格式,WKTReader用来读取WKT文本,mybatis自定义引擎中引入的WKBReader是用来读geohash文本的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Controller
@RequestMapping(value = "/history")
public class MapController {
@Autowired
private MapService mapService;

@ResponseBody
@GetMapping("/pointmodel")
public JSONObject getPoint(@RequestParam("gid") Integer gId){
PointModel pointModel=mapService.getCntyPointByGid(gId).get(0);
JSONObject jsonObject=new JSONObject();
jsonObject.put("gid",pointModel.getGId());
jsonObject.put("namech",pointModel.getNameCh());
JSONObject geometry=new JSONObject();
geometry.put("type",pointModel.getGeometry().getGeometryType());
JSONArray coordinateArray=new JSONArray();
Coordinate[] coordinates=pointModel.getGeometry().getCoordinates();
JSONObject coor=new JSONObject();
coor.put("longitude",coordinates[0].x);
coor.put("latitude",coordinates[0].y);
coordinateArray.add(coor);
geometry.put("coordinate",coordinateArray);
jsonObject.put("geometry",geometry);
return jsonObject;
}

@ResponseBody
@PostMapping("/add")
public int addPoint(@RequestBody JSONObject request){
PointModel pointModel=new PointModel();
pointModel.setGId((Integer) JSONPath.eval(request,"$.gId"));
pointModel.setNameCh((String) JSONPath.eval(request,"$.nameCh"));
String pointStr= (String) JSONPath.eval(request,"$.point");
GeometryFactory geometryFactory = new GeometryFactory();
WKTReader reader = new WKTReader( geometryFactory );
try {
Geometry point = (Point) reader.read(pointStr);
pointModel.setGeometry(point);
} catch (ParseException e) {
e.printStackTrace();
}
return mapService.addCntyPoint(pointModel);
}

@ResponseBody
@PostMapping("/modify")
public int update(@RequestBody JSONObject request){
PointModel pointModel=new PointModel();
pointModel.setGId((Integer) JSONPath.eval(request,"$.gId"));
pointModel.setNameCh((String) JSONPath.eval(request,"$.nameCh"));
String pointStr= (String) JSONPath.eval(request,"$.point");
GeometryFactory geometryFactory = new GeometryFactory();
WKTReader reader = new WKTReader( geometryFactory );
try {
Geometry point = (Point) reader.read(pointStr);
pointModel.setGeometry(point);
} catch (ParseException e) {
e.printStackTrace();
}
return mapService.modifyCntyPoint(pointModel);
}

@ResponseBody
@GetMapping("/remove")
public int removetPoint(@RequestParam("gid") Integer gId){
return mapService.removeCntyPoint(gId);
}
}

dao层、service层都没有需要修改的地方

6 测试

使用postman和postwoman都可以

  • 查询

    GET请求:http://localhost:8080/history/pointmodel?gid=1

  • 增加

    POST请求:http://localhost:8080/history/add

  • 修改

    POST请求:http://localhost:8080/history/modify

  • 删除

    POST请求:http://localhost:8080/history/remove?gid=14357

7 不太懂的代码Tips

  • mybatis包下的GeometryTypeHandler类的作用:

    对于一般的数据类型(如string等),我们不用关注定义的数据是如何映射到MySQL中的char类型的,因为mybatis已经为我们做好了这件事;But,mybatis没有做空间几何数据(点线面)的映射处理,所以我们需要自定义一个类型处理器TypeHandler来做这一层映射,实现特殊对象与sql字段的映射关系!

  • WKT,是一种文本标记语言,用于表示矢量几何对象(点、线、多边形、TIN<不规则三角网>、多面体)、空间参照系统及空间参照系统之间的转换;可以通过几何集合的方式来表示不同维度的几何对象,几何物体的坐标可以是2D(x,y),3D(x,y,z),4D(x,y,z,m),加上一个属于线性参照系统的m值。

报错

报错1

1
Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

先是在postwoman中直接POST请求http://localhost:8080/history/add和请求http://localhost:8080/history/add?gid=14357&nameCh=test&point(109.221,80.287)都报如上的错误!一直就以为是json数据传输和接收有问题,也没找到解决办法。

想着有可能也会是这个postwoman平台的问题,接着下载了postman软件,当输入上述同样的请求后,报了如下的错误:

1
Required request body is missing: public int com.cc.controller.MapController.addPoint(com.alibaba.fastjson.JSONObject)]

即请求的主题内容不存在,这个接口的请求方式是post,是需要从前端提交到后端的,请求的body应该以json的形式写到raw里面

在postwoman中也可以提交,之前一直想的是就在url中提交应该就可以了,以前也是在url中提交的,but,你的代码里面并没有把id/nameCh这些东西写到你提交的url之中

【这个问题困扰了我大概三四天了,最后发现问题出在哪儿的时候,真的想骂自己一句蠢笨如猪!!!归根结底还是自己还有很多专业基础知识缺乏,还是菜鸡!!!编程还是没入门吧~】

参考链接:Required request body is missing 错误解决_IBLiplus的博客-CSDN博客

报错2

1
template might not exist or might not be accessible by any of the configured Template Resolvers\r\n\

在Controller中缺少@ResponseBody注解

参考链接:(38条消息) 错误:template might not exist or might not be accessible by any of the configured Template Resolvers_群鸿的博客-CSDN博客