MyBatis-Plus + PostGIS 实战(五):批量导入与空间数据初始化
一、开篇痛点前面我们解决了Geometry 怎么存第一篇怎么展示1.1 篇 第 4 篇怎么查第 2 篇怎么翻页第 3 篇但有一个问题一直没解决数据从哪来二、三种方案总览方案速度适用场景难度PostgreSQL COPY⭐⭐⭐⭐⭐大数据量首选★★☆☆☆MP saveBatch⭐⭐⭐中小数据 / API 导入★☆☆☆☆ogr2ogr⭐⭐⭐⭐Shapefile 导入★★★☆☆三、方案一PostgreSQL COPY最快3.1 CSV 文件以下面这个csv为例name,category,lon,lat 天安门,景点,116.397,39.903 故宫,景点,116.397,39.916 王府井,商圈,116.417,39.914 西单,商圈,116.375,39.913 颐和园,景点,116.275,39.999保存时编码选 UTF-8避免中文乱码3.2 建临时表CREATETABLEpoi_temp(nameTEXT,categoryTEXT,lonDOUBLEPRECISION,latDOUBLEPRECISION);3.3 COPY 导入数据Windows 本地开发推荐使用\copy客户端复制避免 PostgreSQL 服务权限问题\copy poi_temp(name,category,lon,lat)FROMD:\testw\tests.csvWITH(FORMAT csv,HEADER,DELIMITER,);3.4 转换为 GeometryINSERTINTOpoi(name,category,geom)SELECTname,category,ST_SetSRID(ST_MakePoint(lon,lat),4326)::geometry(Point,4326)FROMpoi_temp;3.5 清理与建索引DROPTABLEpoi_temp;CREATEINDEXidx_poi_geomONpoiUSINGGIST(geom);四、方案二MyBatis-Plus saveBatch灵活4.1 Import DTOpublicclassPoiImportDto{privateStringname;privateStringcategory;privateDoublelon;privateDoublelat;// getter / setter}4.2 Service 实现OverrideTransactionalpublicintbatchImport(ListPoiImportDtolist){ListPoiEntityentitieslist.stream().map(dto-{PoiEntityenewPoiEntity();e.setName(dto.getName());e.setCategory(dto.getCategory());StringwktPOINT(dto.getLon() dto.getLat());try{GeometrygeomnewWKTReader().read(wkt);geom.setSRID(4326);e.setGeom(geom);}catch(ParseExceptionex){thrownewRuntimeException(Invalid WKT: wkt,ex);}returne;}).collect(Collectors.toList());saveBatch(entities,1000);// 每批 1000 条returnentities.size();}4.3 ControllerPostMapping(/import)publicRIntegerimportPoi(RequestBodyListPoiImportDtolist){returnR.ok(poiService.batchImport(list));}4.4 curl 测试curl-XPOST http://localhost:8080/poi/import\-HContent-Type: application/json\-d[ {name:天安门,category:景点,lon:116.397,lat:39.903}, {name:故宫,category:景点,lon:116.397,lat:39.916} ]⚠️ saveBatch 的 batchSize 设太大如 10000会内存溢出设太小如 100会频繁提交变慢。1000~2000 是甜点区。五、方案三ogr2ogrShapefile 导入5.1 安装 GDAL# Ubuntuapt-getinstallgdal-bin# Macbrewinstallgdal5.2 查看 Shapefile 信息ogrinfo-sopoi.shp poi5.3 导入命令ogr2ogr-fPostgreSQL\PG:dbnamepostgis_demo userpostgres password123456 hostlocalhost\poi.shp\-nlnpoi\-overwrite\-lcoGEOMETRY_NAMEgeom\-lcoFIDid\-nltPOINT\-s_srsEPSG:4326\-t_srsEPSG:4326参数说明参数含义-f “PostgreSQL”目标格式为 PostgreSQL-nln poi目标表名-overwrite覆盖已有表-lco GEOMETRY_NAMEgeomGeometry 列名-lco FIDid主键列名-nlt POINT几何类型-s_srs / -t_srs源/目标坐标系⚠️ Shapefile 中文乱码加参数--config SHAPE_ENCODING UTF-8这个方案也可以用程序来实现一般生产是采用这种方式写一个使用gdal导入shp至postgis数据库的方法前端通过传一个含shp的压缩文件调用接口将数据发布到数据库这个后面会单独出一期文章六、导入后必做的 4 步验证数据导入后建议做以下四项验证-- 1. 总数SELECTCOUNT(*)FROMpoi;-- 2. SRID 一致性期望结果4326SELECTDISTINCTST_SRID(geom)FROMpoi;-- 3. 几何有效性期望结果0SELECTCOUNT(*)FROMpoiWHERENOTST_IsValid(geom);-- 4. 空间索引是否存在SELECTindexnameFROMpg_indexesWHEREtablenamepoi;验证项期望结果不通过怎么办总数与实际数据量一致检查 CSV 行数 / 编码SRID全部为 4326UPDATE 设置 SRID几何有效性0 条无效排查坐标顺序 / 范围空间索引idx_poi_geom 存在CREATE INDEX七、踩坑复盘⚠️ 坑 1CSV 的经纬度顺序很多人写成 lat, lon但 PostGIS 要的是 lon, lat即 X, Y。写反了数据会跑到非洲去。-- 正确经度在前纬度在后ST_MakePoint(116.397,39.903)--⚠️ 坑 2SRID 忘记设置ST_MakePoint(lon, lat) 不设置 SRID默认是 0。后续空间查询报错Mixed SRID。-- 解决显式设置 SRIDST_SetSRID(ST_MakePoint(lon,lat),4326)::geometry(Point,4326)⚠️ 坑 3saveBatch 的 batchSize设太大如 10000→ 内存溢出。设太小如 100→ 频繁提交慢。1000~2000 是甜点区。⚠️ 坑 4Shapefile 中文乱码中文字段名 / 属性值乱码。解决ogr2ogr 加--config SHAPE_ENCODING UTF-8⚠️ 坑 5事务太大导致超时一次性 saveBatch(50 万条) → 事务超时。解决分批 Transactional 合理 batchSize。⚠️ 坑 6Windows 下 COPY 权限问题在 Windows 上COPY 要求 PostgreSQL 服务账户对目标路径有读取权限。如果报 Permission denied改用\copy客户端复制只需要当前用户权限。-- 服务端复制需要数据库进程权限COPY poi_tempFROM/data/poi.csv...-- 客户端复制只需要当前用户权限推荐本地开发使用\copy poi_tempFROMD:\testw\tests.csv...⚠️ 坑 7Excel 打开 CSV 只占一个单元格直接双击 CSV 用 Excel 打开可能因系统默认分隔符不是英文逗号导致整行被识别为一个字段。解决方案用记事本 / VS Code 创建 CSV或在 Excel 中通过“数据 → 获取数据 → 从文本/CSV”导入并显式指定逗号分隔符。