情景描述
实体类
@Data
@SuperBuilder
@TableName("layers_media_layer_geotif_meta")
@AllArgsConstructor
public class GeotifMetaDO {
/**
* id
*/
@TableId
private String id;
/**
* 文件名称
*/
@TableField("file_name")
private String fileName;
/**
* 空间范围
*/
private Extent extent;
}
GeotifMetaDO 是一个数据对象对应表名 layers_media_layer_geotif_meta
自定义字段类
注意, Extent 是一个自定义的类,来描述空间范围
/**
* 空间范围
*/
@Data
@SuperBuilder
@Jacksonized
public class Extent {
/**
* 最小经度
*/
@Builder.Default
private double xmin = -180.0;
/**
* 最小纬度
*/
@Builder.Default
private double ymin = -90.0;
/**
* 最大经度
*/
@Builder.Default
private double xmax = 180.0;
/**
* 最大纬度
*/
@Builder.Default
private double ymax = 90.0;
}
数据库中的对应列
某些数据库,提供了空间字段的扩展。将数据存储成空间类型,便于日后执行空间计算。
这里以 集成了 PostGIS 的 PG 数据库为例。
ALTER TABLE IF EXISTS public.layers_media_layer_geotif_meta
ADD COLUMN extent polygon;
使用 polygon 作为类型,来存储 extent
插入操作
构建 Mapper
package cn.vunk.geotif.MediaLayer.biz.dal.pgsql;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.vunk.geotif.MediaLayer.biz.dal.dataobject.GeotifMetaDO;
public interface GeotifMetaMapper extends BaseMapper<GeotifMetaDO> {}
执行测试
@SpringBootTest
public class MapperTest {
@Resource
private GeotifMetaMapper geotifMetaMapper;
@Test
public void geotifMetaAdd () {
var extent = Extent.builder()
.xmin(0.0)
.ymin(0.0)
.xmax(10.0)
.ymax(10.0)
.build();
var geotifMeta = GeotifMetaDO.builder()
.fileName("test.tif")
.extent(extent)
.build();
geotifMetaMapper.insert(geotifMeta);
}
}
发生错误
Caused by: java.lang.IllegalStateException: Type handler was null on parameter mapping for property 'extent'. It was either not specified and/or could not be found for the javaType (cn.vunk.esri.core.geometry.Extent) : jdbcType (null) combination.
TypeHandler
实现
全局配置扫描路径
mybatis-plus:
type-handlers-package: cn.vunk.esri.framework.ibatis
实现 ExtentTypeHandler
package cn.vunk.esri.framework.ibatis;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import cn.vunk.esri.core.geometry.Extent;
import net.postgis.jdbc.PGgeometry;
import net.postgis.jdbc.geometry.LinearRing;
import net.postgis.jdbc.geometry.Point;
import net.postgis.jdbc.geometry.Polygon;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@MappedTypes(Extent.class)
public class ExtentTypeHandler extends BaseTypeHandler<Extent> {
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
Extent parameter,
JdbcType jdbcType
) throws SQLException {
Point[] points = new Point[] {
new Point(parameter.getXmin(), parameter.getYmin()),
new Point(parameter.getXmax(), parameter.getYmin()),
new Point(parameter.getXmax(), parameter.getYmax()),
new Point(parameter.getXmin(), parameter.getYmax())
};
LinearRing linearRing = new LinearRing(points);
Polygon polygon = new Polygon(new LinearRing[] { linearRing });
PGgeometry geom = new PGgeometry(polygon);
ps.setString(i, geom.getValue());
}
// ...
}
测试与错误
### Error updating database. Cause: org.postgresql.util.PSQLException: 错误: 字段 "extent" 的类型为 polygon, 但表达式的类型为 character varying
建议:你需要重写或转换表达式
位置:90
### The error may exist in cn/vunk/geotif/MediaLayer/biz/dal/pgsql/GeotifMetaMapper.java (best guess)
### The error may involve cn.vunk.geotif.MediaLayer.biz.dal.pgsql.GeotifMetaMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO layers_media_layer_geotif_meta ( id, file_name, extent ) VALUES ( ?, ?, ? )
### Cause: org.postgresql.util.PSQLException: 错误: 字段 "extent" 的类型为 polygon, 但表达式的类型为 character varying
建议:你需要重写或转换表达式
位置:90
关键问题
实现上述自定义字段能够成功插入数据库。 是 ExtentTypeHandler 遗漏了什么吗?
如何解决最后的报错
### Error updating database. Cause: org.postgresql.util.PSQLException: 错误: 字段 "extent" 的类型为 polygon, 但表达式的类型为 character varying
建议:你需要重写或转换表达式
位置:90
解决方案
插入时,使用 setObject 插入 PGpolygon 实例
import org.postgresql.geometric.PGpolygon;
// ...
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
Extent parameter,
JdbcType jdbcType
) throws SQLException {
PGpoint[] pgpoints = new PGpoint[4];
pgpoints[0] = new PGpoint(parameter.getXmin(), parameter.getYmin());
pgpoints[1] = new PGpoint(parameter.getXmax(), parameter.getYmin());
pgpoints[2] = new PGpoint(parameter.getXmax(), parameter.getYmax());
pgpoints[3] = new PGpoint(parameter.getXmin(), parameter.getYmax());
PGpolygon pgpolygon = new PGpolygon(pgpoints);
ps.setObject(i, pgpolygon);
}
简单查看 PGpolygon 的实现
public PGpolygon() {
this.type = "polygon";
}
public @Nullable String getValue() {
PGpoint[] points = this.points;
if (points == null) {
return null;
} else {
StringBuilder b = new StringBuilder();
b.append("(");
for(int p = 0; p < points.length; ++p) {
if (p > 0) {
b.append(",");
}
b.append(points[p].toString());
}
b.append(")");
return b.toString();
}
}
这是实际上在指导框架,如何执行 sql 将对应数据插入数据库。
完整的实现
package cn.vunk.esri.framework.ibatis;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.postgresql.geometric.PGpoint;
import org.postgresql.geometric.PGpolygon;
import cn.vunk.esri.core.geometry.Extent;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@MappedTypes(Extent.class)
@MappedJdbcTypes(JdbcType.OTHER)
public class ExtentTypeHandler extends BaseTypeHandler<Extent> {
@Override
public void setNonNullParameter(
PreparedStatement ps,
int i,
Extent parameter,
JdbcType jdbcType
) throws SQLException {
PGpoint[] pgpoints = new PGpoint[4];
pgpoints[0] = new PGpoint(parameter.getXmin(), parameter.getYmin());
pgpoints[1] = new PGpoint(parameter.getXmax(), parameter.getYmin());
pgpoints[2] = new PGpoint(parameter.getXmax(), parameter.getYmax());
pgpoints[3] = new PGpoint(parameter.getXmin(), parameter.getYmax());
PGpolygon pgpolygon = new PGpolygon(pgpoints);
ps.setObject(i, pgpolygon);
}
private Extent stringToExtent (String string) throws SQLException {
var polygon = new PGpolygon(string);
var extent = Extent.builder()
.xmin(polygon.points[0].x)
.ymin(polygon.points[0].y)
.xmax(polygon.points[2].x)
.ymax(polygon.points[2].y)
.build();
return extent;
}
@Override
public Extent getNullableResult(
ResultSet rs,
String columnName
) throws SQLException {
// 从ResultSet中获取数据库字段的值,并将其转换为Extent对象
// 例如,从字符串形式的值中解析出Extent对象
String extentValue = rs.getString(columnName);
return stringToExtent(extentValue);
}
@Override
public Extent getNullableResult(
ResultSet rs,
int columnIndex
) throws SQLException {
String string = rs.getString(columnIndex);
return stringToExtent(string);
}
@Override
public Extent getNullableResult(
CallableStatement cs,
int columnIndex
) throws SQLException {
String string = cs.getString(columnIndex);
return stringToExtent(string);
}
}