SpringBoot+MyBatis+MySQL的Point实现范围查找
前言
最近做了一个功能,需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 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(指定范围查找)
- Mapper 接口中定义方法
List<GroupRideInfo> getListByPoint2(Integer distance, GeoPoint point, String ticket);
- 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 < #{distance} and status=1 and ticket=#{ticket}ORDER BY distance_meters asc
</select>
- 调用方法
可以看到,可以在 MyBatis 中像普通类型参数一样使用 POINT 了。上面示例仅列举了查询操作,新增/修改也是可以的。
相关文章:

SpringBoot+MyBatis+MySQL的Point实现范围查找
前言 最近做了一个功能,需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 MySQL,故选择使用 MySQL 中的 Point 实现范围查找功能。ORM 框架用的是 MyBatis,MyBatis 原生并不支持 Point 字段与 POJO 的映射,需要自…...
【Apache Paimon】-- 1 -- Apache Paimon 是什么?
目录 1、简介 2、概览 3、哪些场景可以使用 Paimon 4、周边生态 5、小结 6、参考 1、简介 我们听说过数据仓库、数据湖、数据湖仓,那你听说过流式数据仓库(Stream warehouse,简称:Streamhouse)吗?那我们今天就来解锁看看他们之中的新秀: Apache paimon 到底是什么…...
解决VsCode无法跳转问题
在settings.json中加入以下代码 { "files.associations": { "*.c":"c", "*.h":"c", "*.s":"masm" }, "includePath":[ "${workspaceFold…...
优化C++设计模式:用模板代替虚函数与多态机制
文章目录 0. 引言1. 模板编程替换虚函数和多态的必要性1.1. MISRA C对类型转换和虚函数的规定1.2. 虚函数与多态问题的影响及如何适应MISRA C要求1.3. 模板编程的优势:替代虚函数和多态机制 2. 设计模式改进2.1. 单例模式的改进与静态局部变量的对比(第二种实现) 2.…...

浪浪云轻量服务器搭建vulfocus网络安全靶场
什么是网络安全靶场 网络安全靶场是一个模拟真实网络环境的训练平台,旨在为网络安全专业人员提供一个安全的环境来测试和提高他们的技能。靶场通常包括各种网络设备、操作系统、应用程序和安全工具,允许用户在其中进行攻击和防御练习。以下是网络安全靶…...

C++builder中的人工智能(23):在现代C++ Windows上轻松录制声音
在这篇文章中,我们将探讨如何在现代C Windows上轻松录制声音。声音以波形和数字形式存在,其音量随时间变化。在C Builder中,使用Windows设备进行录音非常简单。要录制声音,在多设备应用程序中,必须使用FMX.Media.hpp头…...
避免误差!Android 中正确计算时间差的方式
在 Android 开发中,计时和计算时间差异是非常常见的需求,比如记录事件发生的间隔、统计应用启动时间、测量网络请求的响应时间等。在实现这些功能时,我们通常需要一个可靠的时间源来确保计时的准确性。那么为什么 Android 推荐使用 SystemClo…...

unity3d————Resources异步加载
知识点一:Resources异步加载是什么? 在Unity中,资源加载可以分为同步加载和异步加载两种方式。同步加载会在主线程中直接进行,如果加载的资源过大,可能会导致程序卡顿,因为从硬盘读取数据到内存并进行处理…...

YOLOv11改进,YOLOv11添加GnConv递归门控卷积,二次创新C3k2结构
摘要 视觉 Transformer 在多种任务中取得了显著的成功,这得益于基于点积自注意力的新空间建模机制。视觉 Transformer 中的关键因素——即输入自适应、长距离和高阶空间交互——也可以通过卷积框架高效实现。作者提出了递归门控卷积(Recursive Gated Convolution,简称 gnCo…...
如何选择国产化CMS来建设政务网站?
在介绍CMS之前,我们先了解国家为什么要网站为什么要完成国产化改造? 1、信创国产化网站建站响应了国家的信息安全战略,支持自主可控的信息技术产业的发展,减少对进口软硬件的依赖,保障国家信息安全。 2、国产替代&…...

C/C++语言基础--initializer_list表达式、tuple元组、pair对组简介
本专栏目的 更新C/C的基础语法,包括C的一些新特性 前言 initializer_list表达式、tuple元组、pair对组再C日常还是比较常用的,尤其是对组在刷算法还是挺好用的,这里做一个简介;这三个语法结合C17的结构化绑定会更好用ÿ…...

paddle表格识别数据制作
数据格式 其中主要数据有两个一个表格结构的检测框,一个是tokens,注意的地方是 1、只能使用双引号,单引号不行 2、使用带引号的地方是tokens里面 "<tr>", "<td", " colspan2", ">",&quo…...
python selenium库的使用:通过兴趣点获取坐标
通过兴趣点获取坐标 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import TimeoutException# 保存Cookies到文件(可选) import pi…...

如何优化Kafka消费者的性能
要优化 Kafka 消费者性能,你可以考虑以下策略: 并行消费:通过增加消费者组中的消费者数量来并行处理更多的消息,从而提升消费速度。 批量消费:配置 fetch.min.bytes 和 fetch.max.wait.ms 参数来控制批量消费的大小和…...
机器学习 决策树
决策树-分类 1 概念 1、决策节点通过条件判断而进行分支选择的节点。如:将某个样本中的属性值(特征值)与决策节点上的值进行比较,从而判断它的流向。 2、叶子节点没有子节点的节点,表示最终的决策结果。 3、决策树的深度所有节点的最大层…...
效益登记册效益管理计划
效益登记册 benefit Register效益管理计划效益登记册汇集并列出项目集计划的效益,用于在项目集的整个持续时间内测量和沟通效益的交付。在效益识别阶段,效益登记册根据项目集商业论证、组织战略计划和其他相关项目集自标而编制。随后,登记册由…...
Go语言的零值可用性:优势与限制
Go语言以其简洁和高效的设计理念而著称,其中之一便是“零值可用”的特性。这一特性使得许多类型在未显式初始化时即可直接安全地使用,大大简化了代码的初始化过程。然而,并非所有类型都支持零值可用,且在使用时也存在一定的限制。…...

【自用】0-1背包问题与完全背包问题的Java实现
引言 背包问题是计算机科学领域的一个经典优化问题,分为多种类型,其中最常见的是0-1背包问题和完全背包问题。这两种问题的核心在于如何在有限的空间内最大化收益,但它们之间存在一些关键的区别:0-1背包问题允许每个物品只能选择…...

HTML5实现俄罗斯方块小游戏
文章目录 1.设计来源1.1 主界面1.2 皮肤风格1.2 游戏中界面1.3 游戏结束界面 2.效果和源码2.1 动态效果2.2 源代码 源码下载 作者:xcLeigh 文章地址:https://blog.csdn.net/weixin_43151418/article/details/143788449 HTML5实现俄罗斯方块小游戏&#x…...
Mybatis官方生成器使用示例
在这篇文章中,我们将通过实际代码示例来说明如何使用 MyBatis Generator (MBG) 来自动化生成 MyBatis 项目所需的实体类、Mapper 接口和 Mapper XML 文件。我们将使用一个 Maven 插件来执行代码生成,并提供详细的配置和解释。 1. MyBatis Generator 简介…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...