Build and develop the scaffolding mybatis custom field type from scratch, taking Mysql spatial data storage as an example

Preface

Recently, there is a demand on the project that needs to store some latitude and longitude information to achieve similar return to points of interest within 5 kilometers (for example: toilets within 5 kilometers around). Taking into account technical proficiency and operation and maintenance costs, the selection is as follows :

  • Persistence layer framework: mybatis-plus

  • Database: Mysql5.7.x

General realization

database

The database creates 2 fields, longitude:, lnglatitude:lat

  • lng: field type decimal(9, 6)

  • lat: field type decimal (9, 6)

Code

class toilet {
    
    
    String name;
    double lng;
    double lat
}

I won’t go into details here, it’s a very common way to achieve

Since it is not a spatial data structure, a spatial index cannot be created, and query performance is likely to encounter bottlenecks.

Custom type + spatial data type

MySQL supports spatial data types.

Spatial data types and functions can be used MyISAM, InnoDB, NDB, and ARCHIVEtables. Used for indexing spatial columns, MyISAMand InnoDBsupports both SPATIALand non- SPATIALindex. Other storage engines support non- SPATIALindex

MySQL underlying storage format

Query the mysql official website, the results are as follows

The actual storage format of Geometry is: 25 bytes in length

  • 4 bytes for integer SRID (0)
  • 1 byte (integer byte order) (1 = little endian)
  • 4 bytes for the integer type information (MySQL using values from 1 to 7 to indicate Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, and GeometryCollection.)
  • 8-byte double-precision X coordinate
  • 8-byte double-precision Y coordinate

For example, it POINT(1 -1)consists of the following 25-byte sequence, each sequence is represented by two hexadecimal digits:

mysql> SET @g = ST_GeomFromText('POINT(1 -1)');
mysql> SELECT LENGTH(@g);
+------------+
| LENGTH(@g) |
+------------+
|         25 |
+------------+
mysql> SELECT HEX(@g);
+----------------------------------------------------+
| HEX(@g)                                            |
+----------------------------------------------------+
| 000000000101000000000000000000F03F000000000000F0BF |
+----------------------------------------------------+
composition size value
SRID 4 bytes 00000000
Byte order 1 byte 01
WKB type 4 bytes 01000000
X coordinate 8 bytes 000000000000F03F
Y coordinate 8 bytes 000000000000F0BF

database

Create a field in the databasecoordinate

  • coordinate: field type point

Code

Entity class

class toilet {
    
    
    String name;
    
	geopoint location;
}
// 自定义数据类型
class geopoint {
    
    
    double lng;
    double lat 
}

GeoPointTypeHandler

@Slf4j
@MappedTypes({
    
    GeoPoint.class})
public class GeoPointTypeHandler extends BaseTypeHandler<GeoPoint> {
    
    
    /**
     * 空间参照标识系 MySQL数据库默认为0
     */
    private static int SRID = 0;
    /**
     * 字节顺序指示符为1或0表示小端或大端存储。小字节序和大字节序分别也称为网络数据表示(NDR)和外部数据表示(XDR)
     */
    private static byte ENDIAN = (byte) 1;


    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, GeoPoint parameter, JdbcType jdbcType) throws SQLException {
    
    
        ps.setBytes(i, to(parameter));
    }

    @Override
    public GeoPoint getNullableResult(ResultSet rs, String columnName) throws SQLException {
    
    
        return parse(rs.getBytes(columnName));
    }

    @Override
    public GeoPoint getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    
    
        return parse(rs.getBytes(columnIndex));
    }

    @Override
    public GeoPoint getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    
    
        return parse(cs.getBytes(columnIndex));
    }

    /**
     * bytes转GeoPoint对象
     *
     * @param bytes
     */
    private GeoPoint parse(byte[] bytes) {
    
    
        ByteBuffer wrap = ByteBuffer.wrap(bytes)
                // 小端点排序(Java默认是大端点排序,这里要改下)
                .order(ByteOrder.LITTLE_ENDIAN);
        int SRID = wrap.getInt();
        byte endian = wrap.get();
        int wkbType = wrap.getInt();
        double x = wrap.getDouble();
        double y = wrap.getDouble();
        GeoPoint geoPoint = new GeoPoint(x, y);
        log.info("geo-point:{}", JSONUtil.toJsonStr(geoPoint));
        return geoPoint;
    }

    /**
     * GeoPoint转bytes对象
     *
     * @param geoPoint
     */
    private byte[] to(GeoPoint geoPoint) {
    
    
        ByteBuffer wrap = ByteBuffer.allocate(25)
                // 小端点排序(Java默认是大端点排序,这里要改下)
                .order(ByteOrder.LITTLE_ENDIAN);
        // SRID: 0
        wrap.putInt(SRID);
        // 字节顺序指示符为1或0表示小端或大端存储。小字节序和大字节序分别也称为网络数据表示(NDR)和外部数据表示(XDR)
        wrap.put(ENDIAN);
        // WKB类型是指示几何类型的代码 wkbType: 1 MySQL使用从1个值至7,以表示 Point,LineString, Polygon,MultiPoint, MultiLineString, MultiPolygon,和 GeometryCollection。
        wrap.putInt(1);
        // X坐标
        wrap.putDouble(geoPoint.getLon());
        // Y坐标
        wrap.putDouble(geoPoint.getLat());
        return wrap.array();
    }
}

Entity class plus related solution

@TableName(autoResultMap = true) // 注意!! 必须开启映射注解
class toilet {
    
    
    String name;
    @TableField(typeHandler = GeoPointTypeHandler.class)
	geopoint location;
}

test

        Toilet toilet = new Toilet();
        toilet.setName("laker");
        toilet.setLocation(new GeoPoint(123.23, 1.2)); // 直接塞实体
        toiletService.save(toilet);
		// 查询
		List<Toilet> toilets = toiletService.list();
	...
        [{
    
    "name":"laker",location:{
    
    "lng":123.23,"lat":1.2}}]

Calculate the distance

The calculation results have been consistent with the calculation distance api provided by AutoNavi.
Gaode calculate distance webapi

SELECT ( st_distance_sphere ( point ( 116.481028,39.989643  ), point ( 114.465302,40.004717 ), 6378137.0 ) ) AS distance

reference:

  • https://dev.mysql.com/doc/refman/5.7/en/gis-data-formats.html

Guess you like

Origin blog.csdn.net/abu935009066/article/details/114943494