SpringBoot+MyBatis+MySQL的Point实现范围查找

devtools/2024/11/20 23:38:50/

前言

最近做了一个功能,需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 MySQL,故选择使用 MySQL 中的 Point 实现范围查找功能。ORM 框架用的是 MyBatis,MyBatis 原生并不支持 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.3
  • MyBatis-Plus 3.4.2
  • MySQL 8.0.26

建表(含 POINT 字段)

create table group_ride_info
(id             bigint unsigned                            not null comment '主键id'primary 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 '是否删除。默认0,1-是,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(value="GroupRideInfo对象", 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 = "是否删除。默认0,1-是,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 BaseTypeHandler<GeoPoint> {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(指定范围查找

  1. Mapper 接口中定义方法
List<GroupRideInfo> getListByPoint2(Integer distance, GeoPoint point, String ticket);
  1. Mapper.xml 中定义查询语句
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.xxx.groupride.domain.po.GroupRideInfo"><id column="id" property="id" /><result column="create_time" property="createTime" /><result column="update_time" property="updateTime" /><result column="create_by" property="createBy" /><result column="update_by" property="updateBy" /><result column="is_delete" property="isDelete" />...<result column="create_point" property="createPoint" />
</resultMap>
<select id="getListByPoint2" resultMap="BaseResultMap">SELECT *,ST_Distance_Sphere(create_point, #{point}) AS distance_metersFROM group_ride_infoHAVING distance_meters &lt; #{distance} and status=1 and ticket=#{ticket}ORDER BY distance_meters asc
</select>
  1. 调用方法

可以看到,可以在 MyBatis 中像普通类型参数一样使用 POINT 了。上面示例仅列举了查询操作,新增/修改也是可以的。


http://www.ppmy.cn/devtools/135608.html

相关文章

蓝桥杯第22场小白入门赛

刚好牛客周赛和蓝桥杯周赛时间撞了&#xff0c;于是毅然决然选择了打牛客&#xff0c;结局是本蒟蒻被牛客薄纱/(ㄒoㄒ)/~~ 好吧&#xff0c;然后第二天就来水一水蓝桥杯了&#xff08;bushi)&#xff0c;小白入门赛和前几次一样&#xff0c;6道题只能写5道&#xff08;什么时候…

【前端工程化】抽离CSS文件,告别style-loader️(8)

在 Webpack 中&#xff0c;默认情况下&#xff0c;CSS 代码会被 css-loader 转换后&#xff0c;交给 style-loader 处理。style-loader 的工作方式是将样式插入到 <style> 标签中&#xff0c;这对于开发模式来说是非常方便的&#xff0c;因为它可以实现热更新。但在生产环…

常用Adb 命令

# 连接设备 adb connect 192.168.10.125# 断开连接 adb disconnect 192.168.10.125# 查看已连接的设备 adb devices# 安装webview adb install -r "D:\webview\com.google.android.webview_103.0.5060.129-506012903_minAPI23(arm64-v8a,armeabi-v7a)(nodpi)_apkmirror.co…

css3中的多列布局,用于实现文字像报纸一样的布局

作用&#xff1a;专门用于实现类似于报纸类的布局 常用的属性如下&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevic…

《深入理解 Spring MVC 工作流程》

一、Spring MVC 架构概述 Spring MVC 是一个基于 Java 的轻量级 Web 应用框架&#xff0c;它遵循了经典的 MVC&#xff08;Model-View-Controller&#xff09;设计模式&#xff0c;将请求、响应和业务逻辑分离&#xff0c;从而构建出灵活可维护的 Web 应用程序。 在 Spring MV…

PyCharm 中的【控制台】和【终端】的区别

pycharm专业版-使用 PyCharm 中的【控制台】和【终端】的区别如下&#xff1a; 1.环境&#xff1a;控制台是 PyCharm 的内部环境&#xff0c; 终端 是操作系统的命令行界面。 2.功能&#xff1a;控制台可以运行 Python 代码&#xff0c;并显示执行结果&#xff1b; 终端可以…

【flutter】flutter2升级到3.

文章目录 背景flutter2-3升级的修改之处界面效果其它 背景 以这个 https://github.com/aa286211636/Flutter_QQ 为例子&#xff0c; 升级下看看 flutter2-3升级的修改之处 flatButton变为TextButton设备屏幕尺寸获取: Screen.width(context)变为MediaQuery.of(context).size…

SpringCloud处理Websocket消息过长自动断开连接

SpringCloud处理Websocket消息过长自动断开连接 问题描述 近期实现了客户端订阅Websocket后&#xff0c;服务端定期向客户端推送相关设备消息的功能&#xff0c;在本地测试没有问题&#xff0c;上线后却发现订阅设备数量超过一定数量后Websocket就会自动断开连接 报错日志 ja…