扩展mybatis和通用mapper,支持mysql的geometry类型字段
因项目中需要用到地理位置信息的存储、查询、计算等,经过研究决定使用mysql(5.7版本)数据库的geometry类型字段来保存地理位置坐标,使用虚拟列(Virtual Generated Column)来保存geohash值,便于查询。
需要了解geometry如何使用及优势可参看:
mysql中geometry类型的简单使用
MySQL Geometry扩展在地理位置计算中的效率优势
本文主要讲解扩展mybatis和通用mapper,使其支持geometry类型字段的新增、修改、查询
首先创建一张表,作为本文的案例
CREATE TABLE `t_user` (
`id` varchar(45) NOT NULL,
`name` varchar(10) NOT NULL COMMENT '姓名',
`gis` geometry NOT NULL COMMENT '空间位置信息',
`geohash` varchar(20) GENERATED ALWAYS AS (st_geohash(`gis`,8)) VIRTUAL NOT NULL COMMENT 'geo哈希',
PRIMARY KEY (`id`),
UNIQUE KEY `id` (`id`),
SPATIAL KEY `idx_gis` (`gis`),
KEY `idx_geohash` (`geohash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户';
创建对应的实体类
@Table(name = "t_user")
public class User {
private String id;
private String name;
@Column
private GeoPoint gis;
@VirtualGenerated
private String geohash;
}
其中GeoPoint类型是我们自定义的类型,用来对应mysql的geometry类型
public class GeoPoint {
public GeoPoint(BigDecimal lng, BigDecimal lat) {
this.lng = lng;
this.lat = lat;
}
/* 经度 */
private BigDecimal lng;
/* 纬度 */
private BigDecimal lat;
}
@VirtualGenerated注解是我们自定义的注解,用来标识虚拟列字段,使insert、update时能够忽略该字段
使tk通用mapper的insert支持geometry类型
tk通用mapper默认生成的insert语句xml是这样
<insert>
INSERT INTO t_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="name != null">name,</if>
<if test="gis != null">gis,</if>
</trim>
<trim prefix="VALUES(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="name != null">#{name},</if>
<if test="gis != null">#{gis},</if>
</trim>
</insert>
而我们希望生成的insert语句xml是这样
<insert>
INSERT INTO t_user
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="name != null">name,</if>
<if test="gis != null">gis,</if>
</trim>
<trim prefix="VALUES(" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="name != null">#{name},</if>
<if test="gis != null">geomfromtext('point(${gis.lng} ${gis.lat})'),</if>
</trim>
</insert>
于是...开始我们的修改,查看通用mapper的源码得知,通用insert主要是通过BaseInsertMapper和BaseInsertProvider这两个类实现的,所以我们仿造着创建GeoBaseInsertMapper.java 和 GeoBaseInsertProvider.java,其中GeoBaseInsertProvider.java直接复制BaseInsertProvider来修改即可
GeoBaseInsertMapper.java如下:
@RegisterMapper
public interface GeoBaseInsertMapper<T> {
@InsertProvider(type = GeoBaseInsertProvider.class, method = "dynamicSQL")
int insert(T record);
@InsertProvider(type = GeoBaseInsertProvider.class, method = "dynamicSQL")
int insertSelective(T record);
}
最主要的是GeoBaseInsertProvider.java
将
public class GeoBaseInsertProvider extends MapperTemplate {
public GeoBaseInsertProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
super(mapperClass, mapperHelper);
}
public String insert(MappedStatement ms) {
Class<?> entityClass = getEntityClass(ms);
StringBuilder sql = new StringBuilder();
//获取全部列
Set<EntityColumn> columnList = EntityHelper.getColumns(entityClass);
EntityColumn logicDeleteColumn = SqlHelper.getLogicDeleteColumn(entityClass);
processKey(sql, entityClass, ms, columnList);
sql.append(SqlHelper.insertIntoTable(entityClass, tableName(entityClass)));
sql.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
//当某个列有主键策略时,不需要考虑他的属性是否为空,因为如果为空,一定会根据主键策略给他生成一个值
for (EntityColumn column : columnList) {
if (!column.isInsertable()) {
continue;
}
//忽略虚拟列
if (column.getEntityField().isAnnotationPresent(VirtualGenerated.class)) {
continue;
}
sql.append(column.getColumn() + ",");
}
sql.append("</trim>");
sql.append("<trim prefix=\"VALUES(\" suffix=\")\" suffixOverrides=\",\">");
for (EntityColumn column : columnList) {
if (!column.isInsertable()) {
continue;
}
//忽略虚拟列
if (column.getEntityField().isAnnotationPresent(VirtualGenerated.class)) {
continue;
}
if (logicDeleteColumn != null && logicDeleteColumn == column) {
sql.append(SqlHelper.getLogicDeletedValue(column, false)).append(",");
continue;
}
//优先使用传入的属性值,当原属性property!=null时,用原属性
//自增的情况下,如果默认有值,就会备份到property_cache中,所以这里需要先判断备份的值是否存在
if (column.isIdentity()) {
sql.append(SqlHelper.getIfCacheNotNull(column, column.getColumnHolder(null, "_cache", ",")));
} else {
//判断字段是GeoPoint类型时,调用getGeoColumnHolder方法来生成
if (column.getJavaType() == GeoPoint.class) {
//<if test="property != null">geomfromtext('point(108.9498710632 34.2588125935)'),</if>
sql.append(SqlHelper.getIfNotNull(column, getGeoColumnHolder(column), isNotEmpty()));
} else {
//其他情况值仍然存在原property中
sql.append(SqlHelper.getIfNotNull(column, column.getColumnHolder(null, null, ","), isNotEmpty()));
}
}
//当属性为null时,如果存在主键策略,会自动获取值,如果不存在,则使用null
if (column.isIdentity()) {
sql.append(SqlHelper.getIfCacheIsNull(column, column.getColumnHolder() + ","));
} else {
//判断字段是GeoPoint类型时,调用getGeoColumnHolder方法来生成
if (column.getJavaType() == GeoPoint.class) {
//<if test="property == null">geomfromtext('point(108.9498710632 34.2588125935)'),</if>
sql.append(SqlHelper.getIfIsNull(column, getGeoColumnHolder(column), isNotEmpty()));
} else {
//当null的时候,如果不指定jdbcType,oracle可能会报异常,指定VARCHAR不影响其他
sql.append(SqlHelper.getIfIsNull(column, column.getColumnHolder(null, null, ","), isNotEmpty()));
}
}
}
sql.append("</trim>");
return sql.toString();
}
/*
* insert GEO字段占位符
*/
private String getGeoColumnHolder(EntityColumn column){
return String.format("geomfromtext('point(${%s.lng} ${%s.lat})'),",column.getProperty(),column.getProperty());
}
//忽略以下部分代码
}
让你的mapper接口继承GeoBaseInsertMapper就能使insert方法支持geometry类型了,同时能够忽略虚拟列。
@Repository
public interface UserMapper extends GeoBaseInsertMapper<User>{
}
如果你理解了通用insert的修改,update的修改也同样如此,相信难不倒你,这里就不再贴代码了。
使mybatis查询支持将geometry类型字段映射到GeoPoint类型
mybatis通过定义typeHandler将数据类型映射为java类型,mybatis内置了多种常见的typeHandler,但没有支持geometry,好在mybatis提供了足够的扩展性,我们可以自定义typeHandler,这里还需要在pom.xml引入jts库来解析
<dependency>
<groupId>com.vividsolutions</groupId>
<artifactId>jts</artifactId>
<version>${jts.version}</version>
</dependency>
接下来是自定义的MysqlGeoPointTypeHandler
/*
* mybatis查询结果集中 mysql的geometry类型映射到GeoPoint对象
*/
@MappedTypes(value = {GeoPoint.class})
public class MysqlGeoPointTypeHandler extends BaseTypeHandler<GeoPoint> {
private WKBReader _wkbReader;
public MysqlGeoPointTypeHandler(int srid) {
GeometryFactory _geometryFactory = new GeometryFactory(new PrecisionModel(), srid);
_wkbReader = new WKBReader(_geometryFactory);
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, GeoPoint parameter, JdbcType jdbcType) {
//因为GeoPoint对象里包含经度和纬度两个值,无法直接适配到一个参数,所以也不会使用到这个方法
}
@Override
public GeoPoint getNullableResult(ResultSet rs, String columnName) throws SQLException {
return fromMysqlWkb(rs.getBytes(columnName));
}
@Override
public GeoPoint getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return fromMysqlWkb(rs.getBytes(columnIndex));
}
@Override
public GeoPoint getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return fromMysqlWkb(cs.getBytes(columnIndex));
}
/*
* bytes转GeoPoint对象
*/
private GeoPoint fromMysqlWkb(byte[] bytes) {
if (bytes == null) {
return null;
}
try {
byte[] geomBytes = ByteBuffer.allocate(bytes.length - 4).order(ByteOrder.LITTLE_ENDIAN)
.put(bytes, 4, bytes.length - 4).array();
Geometry geometry = _wkbReader.read(geomBytes);
Point point = (Point) geometry;
return new GeoPoint(new BigDecimal(String.valueOf(point.getX())), new BigDecimal(String.valueOf(point.getY())));
} catch (Exception e) {
}
return null;
}
}
然后我们需要将MysqlGeoPointTypeHandler添加到mybatis配置中,这样mybatis在遇到GeoPoint时就知道怎么映射了。
这里演示用java代码来配置mybatis,也可以在mybatis.xml文件中配置
@Configuration
@MapperScan(basePackages = {"com.carson.**.mapper"}, sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setVfs(SpringBootVFS.class);
//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:mybatis/**/*Mapper.xml"));
bean.setTypeAliasesPackage("com.carson.pojo");
//添加MysqlGeoPointTypeHandler
bean.setTypeHandlers(new TypeHandler[]{new MysqlGeoPointTypeHandler()});
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
完成这些以后查询的结果集里包含geometry类型的字段,就能映射到GeoPoint了,从而可以获取经纬度
源码在哪里? talk is cheap,show me the code!
如果你懒得看以上长篇大论,只想要开箱即用的代码,就在这里了,有帮助的话记得给个star哦!
https://github.com/tzjzcy/mybatis-mysql-geo-boot
https://gitee.com/tzjzcy/mybatis-mysql-geo-boot
扩展mybatis和通用mapper,支持mysql的geometry类型字段的更多相关文章
- Mybatis整合通用Dao,Mybatis整合通用Mapper,MyBatis3.x整合通用 Mapper3.5.x
Mybatis整合通用Dao,Mybatis整合通用Mapper,MyBatis3.x整合通用 Mapper3.5.x ============================== 蕃薯耀 2018年 ...
- MyBatis插件 - 通用mapper
1.简单认识通用mapper 1.1.了解mapper 作用:就是为了帮助我们自动的生成sql语句 [ ps:MyBatis需要编写xxxMapper.xml,而逆向工程是根据entity实体类来进行 ...
- mysql中geometry类型的简单使用
mysql中geometry类型的简单使用 编写本文的目的: 让和两天前的我一样的初学者,能够更快的使用geometry类型存储空间点数据 也是为了自己加深印象,更熟练的使用geometry类型 ...
- (二、下) springBoot 、maven 、mysql、 mybatis、 通用Mapper、lombok 简单搭建例子 《附项目源码》
接着上篇文章中 继续前进. 一.在maven 的pom.xm中添加组件依赖, mybatis通用Mapper,及分页插件 1.mybatis通用Mapper <!-- mybatis通用Mapp ...
- Spring Boot集成Mybatis及通用Mapper
集成Mybatis可以通过 mybatis-spring-boot-starter 实现. <!-- https://mvnrepository.com/artifact/org.mybatis ...
- 基于Dapper的开源Lambda扩展LnskyDB 3.0已支持Mysql数据库
LnskyDB LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼.,现在已经支持MySql和Sql serv ...
- 关于Java读取mysql中date类型字段默认值'0000-00-00'的问题
今天在做项目过程中,查询一个表中数据时总碰到这个问题: java.sql.SQLException:Value '0000-00-00' can not be represented as ...
- mybatis的通用mapper小结
import tk.mybatis.mapper.entity.Example; //此包是tk下的1.定义一个dao层接口不需要任何方法 需要继承Mapper<类型> 2.在servic ...
- 【转】mysql 中int类型字段unsigned和signed的区别
转自https://www.cnblogs.com/wangzhongqiu/p/6424827.html 用法: mysql> CREATE TABLE t ( a INT UNSIGNED, ...
随机推荐
- Go中的fmt几种输出的区别和格式化方式
在日常使用fmt包的过程中,各种眼花缭乱的print是否让你莫名的不知所措呢,更让你茫然的是各种格式化的占位符..简直就是噩梦.今天就让我们来征服格式化输出,做一个会输出的Goer. fmt.Prin ...
- 还在为垂直居中苦恼?CSS 布局利器 flexbox 轻轻松松帮你搞定
传统的 CSS 布局方式是基于盒模型(它是根据盒子与父盒子以及兄弟盒子的关系确定大小和位置的算法),实现时依赖于 block, inline, table, position, float 这些属性, ...
- 使用selenium进行爬取掘金前端小册的数据
Selenium 简介 百度百科介绍: Selenium [1] 是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE(7, 8, ...
- 再次学习Git版本控制工具
Git 究竟是怎样的一个系统呢?为什么在SVN作为版本控制工具已经非常流行的时候,还有Git这样一个版本控制工具呢?Git和SVN的区别在哪儿呢?Git优势又在哪呢?下面PHP程序员雷雪松带你一起详细 ...
- LoRaWAN_stack移植笔记(七)_数据包的接收发送
以下的代码适用于LoRa sx1276点对点的通讯,纯粹的考虑在非发射模式下即为接收模式 配置sx1276的射频参数,并且切换到接收模式 //bandwidth [0:125 1:250 2:500] ...
- Linux网络问题排错
前言 作为一名软件工程师,Linux相关的知识是一个不可或缺的技能点,而网络问题往往是初学者接触Linux时最先碰到的一只拦路虎,本篇博客将系统的讲解一个解决Linux网络问题的通用方法论,一个科学的 ...
- MySQL多表关联数据同时删除
MySQL多表关联时的多表删除: DELETE t1, t2FROM t1LEFT JOIN t2 ON t1.id = t2.idWHERE t1.id = 25
- 如何编写高质量的 JS 函数(1) -- 敲山震虎篇
本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/7lCK9cHmunvYlbm7Xi7JxQ作者:杨昆 一千个读者,有一千个哈姆雷特. 此系列文 ...
- Oracle数据库之Oracle的下载与安装
二.Oracle 的下载与安装 2.1.Oracle 简介 Oracle 公司是全球最大的信息管理软件及服务供应商,成立于 1977 年,主要的业务是推动电子商务平台的搭建.Oracle 公司有自己的 ...
- 随笔编号-12 阿里云CentOS7系列一 -- 安装JDK7的方法.
最近因为数据采集以及生产环境冲突.导入windows Server 2008系统经常死机.经讨论决定把采集服务程序和生产服务进行分开.采集程序通过windows Server2008运行.而生产程序通 ...