自适应网站建站,深圳企业网站建设维护,四博互联做的网站,做网站还是app好前言
最近做了一个功能#xff0c;需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 MySQL#xff0c;故选择使用 MySQL 中的 Point 实现范围查找功能。ORM 框架用的是 MyBatis#xff0c;MyBatis 原生并不支持 Point 字段与 POJO 的映射#xff0c;需要自…前言
最近做了一个功能需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 MySQL故选择使用 MySQL 中的 Point 实现范围查找功能。ORM 框架用的是 MyBatisMyBatis 原生并不支持 Point 字段与 POJO 的映射需要自定义 MyBatis 的 TypeHandler 实现该功能。
当然你可以通过定义两个 MySQL 字段经度和维度来代替 Point 也可以实现范围查找但是既然是使用的 MyBatis那么还是希望能在 MyBatis 中直接操作 Point提高代码通用性。
关于 MySQL 的 POINT
在MySQL中POINT 是一种用于存储地理空间数据的数据类型它表示二维空间中的一个点。MySQL 从 5.7 版本开始提供了对地理空间数据类型的原生支持包括 POINT、LINESTRING、POLYGON 等。
POINT 数据类型用于存储一个二维坐标点其格式为 (X, Y)其中 X 和 Y 分别表示该点在二维平面上的横坐标和纵坐标。
注意在用 POINT 存储经纬度时X 为经度Y 为纬度不要弄反了。因为将经纬度存储到 POINT 时并没有循序限制但是使用 POINT 相关函数时就有限制了。比如ST_Distance_Sphere。
组件版本
SpringBoot 2.4.3MyBatis-Plus 3.4.2MySQL 8.0.26
建表含 POINT 字段
create table group_ride_info
(id bigint unsigned not null comment 主键idprimary key,create_time datetime not null comment 创建时间,update_time datetime default CURRENT_TIMESTAMP null on update CURRENT_TIMESTAMP comment 修改时间,create_by int unsigned default 0 not null comment 创建人,update_by int unsigned default 0 not null comment 修改人,is_delete tinyint unsigned default 0 not null comment 是否删除。默认01-是0-否,...create_point point not null comment 创建时坐标
)comment 团信息表;create spatial index create_pointon group_ride_info (create_point);其中create_point 是通过 POINT 字段记录的经纬度坐标POINT 字段建议设置为为空。同时需要给 PIOINT 类型字段创建空间索引。
alter table group_ride_info add SPATIAL index(create_point);定义 GeoPoint 对象
Builder
AllArgsConstructor
Data
public class GeoPoint implements Serializable {/*** 经度*/private Double longitude;/*** 纬度*/private Double latitude;
}定义表对象
Data
EqualsAndHashCode(callSuper false)
Accessors(chain true)
TableName(group_ride_info)
ApiModel(valueGroupRideInfo对象, description团信息表)
public class GroupRideInfo implements Serializable {private static final long serialVersionUID 1L;ApiModelProperty(value 主键id)TableId(value id, type IdType.NONE)private Long id;ApiModelProperty(value 创建时间)private LocalDateTime createTime;ApiModelProperty(value 修改时间)private LocalDateTime updateTime;ApiModelProperty(value 创建人)private Integer createBy;ApiModelProperty(value 修改人)private Integer updateBy;ApiModelProperty(value 是否删除。默认01-是0-否)private Integer isDelete;...ApiModelProperty(value 创建时坐标)private GeoPoint createPoint;}定义坐标转换器 GeoPointConverter
public class GeoPointConverter {/*** Little endian or Big endian*/private int byteOrder ByteOrderValues.LITTLE_ENDIAN;/*** Precision model*/private PrecisionModel precisionModel new PrecisionModel();/*** Coordinate sequence factory*/private CoordinateSequenceFactory coordinateSequenceFactory CoordinateArraySequenceFactory.instance();/*** Output dimension*/private int outputDimension 2;/*** Convert byte array containing SRID WKB Geometry into Geometry object*/public GeoPoint from(byte[] bytes) {if (bytes null) {return null;}try (ByteArrayInputStream inputStream new ByteArrayInputStream(bytes)) {// Read SRIDbyte[] sridBytes new byte[4];inputStream.read(sridBytes);int srid ByteOrderValues.getInt(sridBytes, byteOrder);// Prepare Geometry factoryGeometryFactory geometryFactory new GeometryFactory(precisionModel, srid, coordinateSequenceFactory);// Read GeometryWKBReader wkbReader new WKBReader(geometryFactory);Geometry geometry wkbReader.read(new InputStreamInStream(inputStream));Point point (Point) geometry;// convert to GeoPointGeoPoint geoPoint new GeoPoint(point.getX(), point.getY());return geoPoint;} catch (IOException | ParseException e) {throw new IllegalArgumentException(e);}}/*** Convert Geometry object into byte array containing SRID WKB Geometry*/public byte[] to(GeoPoint geoPoint) {if (geoPoint null) {return null;}Coordinate coordinate new Coordinate(geoPoint.getLongitude(), geoPoint.getLatitude());CoordinateArraySequence coordinateArraySequence new CoordinateArraySequence(new Coordinate[]{coordinate}, 2);Point point new Point(coordinateArraySequence, new GeometryFactory());try (ByteArrayOutputStream outputStream new ByteArrayOutputStream()) {// Write SRIDbyte[] sridBytes new byte[4];ByteOrderValues.putInt(point.getSRID(), sridBytes, byteOrder);outputStream.write(sridBytes);// Write GeometryWKBWriter wkbWriter new WKBWriter(outputDimension, byteOrder);wkbWriter.write(point, new OutputStreamOutStream(outputStream));return outputStream.toByteArray();} catch (IOException ioe) {throw new IllegalArgumentException(ioe);}}
}定义 GeoPointTypeHandler
MappedTypes({GeoPoint.class})
public class GeoPointTypeHandler extends BaseTypeHandlerGeoPoint {GeoPointConverter converter new GeoPointConverter();Overridepublic void setNonNullParameter(PreparedStatement ps, int i, GeoPoint parameter, JdbcType jdbcType) throws SQLException {ps.setBytes(i, converter.to(parameter));}Overridepublic GeoPoint getNullableResult(ResultSet rs, String columnName) throws SQLException {return converter.from(rs.getBytes(columnName));}Overridepublic GeoPoint getNullableResult(ResultSet rs, int columnIndex) throws SQLException {return converter.from(rs.getBytes(columnIndex));}Overridepublic GeoPoint getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {return converter.from(cs.getBytes(columnIndex));}
}配置扫描 TypeHandler
Configuration
EnableTransactionManagement
MapperScan(basePackages {${spring.xxx.data.db.basepackage}}, sqlSessionFactoryRef sqlSessionFactoryMasterDb)
Slf4j
public class DbConfig implements TransactionManagementConfigurer {/*** 注释** return SqlSessionFactory* throws Exception 异常*/Beanpublic SqlSessionFactory sqlSessionFactoryMasterDb() throws Exception {MybatisSqlSessionFactoryBean factoryBean new MybatisSqlSessionFactoryBean();...// 此处为定义TypeHandler所在的包名factoryBean.setTypeHandlersPackage(com.xxx.module.typehandler);return factoryBean.getObject();}
}注意此处代码仅为示例代码关键代码在factoryBean.setTypeHandlersPackage(com.xxx.module.typehandler);
SpringBoot 项目也可以在配置文件中配置请自行百度目的是让自定义 TypeHandler 生效。
MyBatis 使用 POINT
原生 getById() 自定义 SQL指定范围查找
Mapper 接口中定义方法
ListGroupRideInfo getListByPoint2(Integer distance, GeoPoint point, String ticket);Mapper.xml 中定义查询语句
!-- 通用查询映射结果 --
resultMap idBaseResultMap typecom.xxx.groupride.domain.po.GroupRideInfoid columnid propertyid /result columncreate_time propertycreateTime /result columnupdate_time propertyupdateTime /result columncreate_by propertycreateBy /result columnupdate_by propertyupdateBy /result columnis_delete propertyisDelete /...result columncreate_point propertycreatePoint /
/resultMap
select idgetListByPoint2 resultMapBaseResultMapSELECT *,ST_Distance_Sphere(create_point, #{point}) AS distance_metersFROM group_ride_infoHAVING distance_meters lt; #{distance} and status1 and ticket#{ticket}ORDER BY distance_meters asc
/select调用方法 可以看到可以在 MyBatis 中像普通类型参数一样使用 POINT 了。上面示例仅列举了查询操作新增/修改也是可以的。