java mysql geometry,扩展mybatis和通用mapper,支持mysql的geometry类型字段,mybatis用mapper...

扩展mybatis和通用mapper,支持mysql的geometry类型字段,mybatis用mapper

因项目中需要用到地理位置信息的存储、查询、计算等,经过研究决定使用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 INTO t_user

id,

name,

gis,

#{id},

#{name},

#{gis},

而我们希望生成的insert语句xml是这样

INSERT INTO t_user

id,

name,

gis,

#{id},

#{name},

geomfromtext('point(${gis.lng} ${gis.lat})'),

于是...开始我们的修改,查看通用mapper的源码得知,通用insert主要是通过BaseInsertMapper和BaseInsertProvider这两个类实现的,所以我们仿造着创建GeoBaseInsertMapper.java 和 GeoBaseInsertProvider.java,其中GeoBaseInsertProvider.java直接复制BaseInsertProvider来修改即可

GeoBaseInsertMapper.java如下:

@RegisterMapper

public interface GeoBaseInsertMapper {

@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 columnList = EntityHelper.getColumns(entityClass);

EntityColumn logicDeleteColumn = SqlHelper.getLogicDeleteColumn(entityClass);

processKey(sql, entityClass, ms, columnList);

sql.append(SqlHelper.insertIntoTable(entityClass, tableName(entityClass)));

sql.append("");

//当某个列有主键策略时,不需要考虑他的属性是否为空,因为如果为空,一定会根据主键策略给他生成一个值

for (EntityColumn column : columnList) {

if (!column.isInsertable()) {

continue;

}

//忽略虚拟列

if (column.getEntityField().isAnnotationPresent(VirtualGenerated.class)) {

continue;

}

sql.append(column.getColumn() + ",");

}

sql.append("");

sql.append("");

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) {

//geomfromtext('point(108.9498710632 34.2588125935)'),

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) {

//geomfromtext('point(108.9498710632 34.2588125935)'),

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("");

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{

}

如果你理解了通用insert的修改,update的修改也同样如此,相信难不倒你,这里就不再贴代码了。

使mybatis查询支持将geometry类型字段映射到GeoPoint类型

mybatis通过定义typeHandler将数据类型映射为java类型,mybatis内置了多种常见的typeHandler,但没有支持geometry,好在mybatis提供了足够的扩展性,我们可以自定义typeHandler,这里还需要在pom.xml引入jts库来解析

com.vividsolutions

jts

${jts.version}

接下来是自定义的MysqlGeoPointTypeHandler

/*

* mybatis查询结果集中 mysql的geometry类型映射到GeoPoint对象

*/

@MappedTypes(value = {GeoPoint.class})

public class MysqlGeoPointTypeHandler extends BaseTypeHandler {

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

相关文章暂无相关文章

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值