MyBatis-Plus + PostGIS 实战一(基础篇)

目录

    • 一、背景与目标
    • 二、环境版本说明
    • 三、数据库设计
    • 四、项目结构
    • 五、核心实现
      • [5.1 Entity 设计](#5.1 Entity 设计)
      • [5.2 GeometryTypeHandler 终极实现](#5.2 GeometryTypeHandler 终极实现)
      • [5.3 Mapper 层](#5.3 Mapper 层)
      • [5.4 Service 层(业务逻辑收敛)](#5.4 Service 层(业务逻辑收敛))
      • [5.5 Controller 层(HTTP 边界)](#5.5 Controller 层(HTTP 边界))
    • 六、接口测试示例
      • [6.1 新增 POI](#6.1 新增 POI)
      • [6.2 更新 POI](#6.2 更新 POI)
      • [6.3 删除 POI](#6.3 删除 POI)
      • [6.4 查询详情](#6.4 查询详情)
    • 七、踩坑复盘(重点)
      • [7.1 MyBatis / MyBatis-Plus 自动配置冲突](#7.1 MyBatis / MyBatis-Plus 自动配置冲突)
      • [7.2 Maven Compiler Plugin 版本问题](#7.2 Maven Compiler Plugin 版本问题)
      • [7.3 PostGIS JDBC 返回格式陷阱](#7.3 PostGIS JDBC 返回格式陷阱)
      • [7.4 EWKB SRID Flag 解析](#7.4 EWKB SRID Flag 解析)
      • [7.5 TypeHandler 生效条件](#7.5 TypeHandler 生效条件)
    • 八、最佳实践总结

技术栈:Spring Boot 3.5.5 + MyBatis-Plus 3.5.7 + PostgreSQL / PostGIS + JTS

本文验证 MyBatis-Plus 操作 PostGIS Geometry 字段的完整增删改查流程,并提供 TypeHandler 终极方案与踩坑复盘。

一、背景与目标

在 GIS 相关项目中,空间数据的持久化一直是一个核心需求。PostGIS 作为 PostgreSQL 的空间扩展,提供了强大的空间数据存储和查询能力。而 MyBatis-Plus 作为国内最流行的 ORM 框架之一,如何优雅地与之集成,是本文要解决的核心问题。

本文的目标非常简单:

  • 验证 MyBatis-Plus 能否像操作普通字段一样,操作 PostGIS 的 Geometry 字段。
  • 具体包括:新增 POI(兴趣点)、更新 POI、删除 POI、按 ID 查询详情。

二、环境版本说明

以下版本经过实际验证,可以正常工作:

组件 版本
Spring Boot 3.5.5
MyBatis-Plus 3.5.7
MyBatis-Spring 3.0.3(手动指定)
PostgreSQL / PostGIS 18.4 / 3.6
JTS (JTS Topology Suite) 1.19.0
Java 17
PostgreSQL JDBC Driver 42.7.3
PostGIS JDBC 2024.1.0

⚠️ 关键提示:MyBatis-Plus 3.5.7 自带的 mybatis-spring 是 2.x 版本(适配 Spring Boot 2.x),与 Spring Boot 3.x 不兼容。必须排除低版本并手动引入 mybatis-spring 3.0.3。

三、数据库设计

创建 POI 表:

sql 复制代码
CREATE TABLE poi (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    category VARCHAR(50),
    geom geometry(Point, 4326)
);

字段说明:

字段 类型 说明
id SERIAL 主键,自增
name VARCHAR POI 名称
category VARCHAR 分类(景点 / 美食 / 酒店等)
geom geometry(Point,4326) 空间字段,存储经纬度(WGS84)

SRID 4326 表示 WGS84 坐标系(经度 / 纬度)

四、项目结构

项目采用标准的 Maven 多包结构:

复制代码
src/main/java/org/pgd/postgisdemo/
├── PostgisDemoApplication.java    # 启动类
├── config/
│   └── MyBatisPlusConfig.java    # MyBatis-Plus 配置(分页插件等)
├── controller/
│   └── PoiController.java       # HTTP 接口层
├── service/
│   ├── PoiService.java           # 服务接口
│   └── impl/
│       └── PoiServiceImpl.java   # 服务实现
├── mapper/
│   └── PoiMapper.java           # MyBatis Mapper 接口
├── entity/
│   └── PoiEntity.java           # 数据库实体
├── dto/
│   ├── AddPoiReq.java           # 新增请求 DTO
│   ├── UpdatePoiReq.java        # 更新请求 DTO
│   └── PoiDetailResp.java       # 详情响应 DTO
├── handler/
│   └── GeometryTypeHandler.java # Geometry <-> EWKB 转换器
└── common/
    └── R.java                   # 统一响应封装

src/main/resources/
├── application.yml               # Spring Boot 配置
└── mapper/
    └── PoiMapper.xml            # SQL 映射文件

pom.xml文件

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.5.5</version>
        <relativePath/>
    </parent>
    <groupId>org.pgd</groupId>
    <artifactId>postgis-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>postgis-demo</name>
    <description>postgis-demo</description>
    <packaging>jar</packaging>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <mybatis-plus.version>3.5.7</mybatis-plus.version>
        <mybatis-spring.version>3.0.3</mybatis-spring.version>
        <postgresql.version>42.7.3</postgresql.version>
        <postgis.version>2024.1.0</postgis.version>
        <jts.version>1.19.0</jts.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- MyBatis-Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
            <exclusions>
                <!-- 排除低版本mybatis-spring -->
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- 明确指定适配Spring Boot 3.x的mybatis-spring版本 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${mybatis-spring.version}</version>
        </dependency>
        <!-- PostgreSQL Driver -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>${postgresql.version}</version>
        </dependency>

        <!-- PostGIS JDBC -->
        <dependency>
            <groupId>net.postgis</groupId>
            <artifactId>postgis-jdbc</artifactId>
            <version>${postgis.version}</version>
        </dependency>

        <!-- JTS -->
        <dependency>
            <groupId>org.locationtech.jts</groupId>
            <artifactId>jts-core</artifactId>
            <version>${jts.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml文件

java 复制代码
spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgis_36_sample   # 改成你自己的数据库
    username: postgres
    password: 123456        # 改成你自己的密码
    driver-class-name: org.postgresql.Driver

mybatis-plus:
  mapper-locations: classpath*:mapper/**/*.xml
  type-aliases-package: org.pgd.postgisdemo.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto

五、核心实现

5.1 Entity 设计

Entity 是数据库表的直接映射,Geometry 字段使用 JTS 的 Geometry 类型,配合 @TableField(typeHandler = ...) 指定自定义类型处理器:

java 复制代码
@Data
@TableName(value = "poi", autoResultMap = true)
public class PoiEntity {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String category;
    @TableField(typeHandler = GeometryTypeHandler.class)
    private Geometry geom;

    public PoiEntity() {}

    public PoiEntity(String name, String category, Geometry geom) {
        this.name = name;
        this.category = category;
        this.geom = geom;
    }
}

关键点@TableName(autoResultMap = true) 是让 MyBatis-Plus 自动生成 resultMap 并应用 @TableField 上声明的 TypeHandler 的开关。没有它,Geometry 字段会始终为 null。

5.2 GeometryTypeHandler 终极实现

这是整个项目的核心。PostGIS JDBC 驱动默认以十六进制字符串形式返回 geometry 列(EWKB 编码),Handler 需要同时支持 EWKB 和 WKT 两种格式:

java 复制代码
@MappedTypes(Geometry.class)
public class GeometryTypeHandler extends BaseTypeHandler<Geometry> {

    private static final WKBReader WKB_READER = new WKBReader();
    private static final WKTReader WKT_READER = new WKTReader();
    private static final WKBWriter WKB_WRITER =
            new WKBWriter(2, ByteOrderValues.LITTLE_ENDIAN, true);

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
                                    Geometry geometry, JdbcType jdbcType)
            throws SQLException {
        byte[] wkb = WKB_WRITER.write(geometry);
        ps.setBytes(i, wkb);
    }

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

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

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

    /**
     * 核心解析:支持两种格式
     * 1. WKT 文本(如 POINT(116.397 39.903))
     * 2. EWKB 十六进制字符串(如 0101000020E6100000...)
     */
    private Geometry parse(String value) throws SQLException {
        if (value == null || value.isEmpty()) {
            return null;
        }

        try {
            // 尝试 WKT 解析(以数字或 POINT/LINESTRING/POLYGON 开头)
            if (value.startsWith("POINT") || value.startsWith("LINESTRING")
                    || value.startsWith("POLYGON") || value.startsWith("MULTI")) {
                return WKT_READER.read(value);
            }

            // 尝试 EWKB 十六进制解析
            byte[] bytes = hexToBytes(value);
            return parseEwkb(bytes);

        } catch (ParseException e) {
            throw new SQLException("Failed to parse geometry: " + value, e);
        }
    }

    private Geometry parseEwkb(byte[] bytes) throws ParseException {
        if (bytes == null || bytes.length < 5) {
            return null;
        }

        ByteBuffer buffer = ByteBuffer.wrap(bytes);

        // Byte 0: byte order
        byte bo = buffer.get();
        buffer.order(bo == 0
                ? ByteOrder.BIG_ENDIAN
                : ByteOrder.LITTLE_ENDIAN);

        // Bytes 1-4: EWKB type word
        int typeWord = buffer.getInt();

        // Extract SRID if present
        int srid = 0;
        if ((typeWord & 0x20000000) != 0) {
            srid = buffer.getInt();
        }

        // Remaining: pure coordinate data (no type word)
        byte[] coordsOnly = new byte[buffer.remaining()];
        buffer.get(coordsOnly);

        // Reconstruct standard WKB:
        // WKB = byte_order(1) + type_word(4) + coordinates
        // The type word without the hasSRID flag:
        int wkbTypeWord = typeWord & 0xDFFFFFFF; // clear hasSRID bit

        // Build standard WKB bytes:
        // [byte_order][type_word(4)][coordinates...]
        byte[] standardWkb = new byte[1 + 4 + coordsOnly.length];
        standardWkb[0] = bo; // byte order
        // Write type word in correct byte order
        if (bo == 0) {
            // Big endian
            standardWkb[1] = (byte) ((wkbTypeWord >> 24) & 0xFF);
            standardWkb[2] = (byte) ((wkbTypeWord >> 16) & 0xFF);
            standardWkb[3] = (byte) ((wkbTypeWord >> 8) & 0xFF);
            standardWkb[4] = (byte) (wkbTypeWord & 0xFF);
        } else {
            // Little endian
            standardWkb[1] = (byte) (wkbTypeWord & 0xFF);
            standardWkb[2] = (byte) ((wkbTypeWord >> 8) & 0xFF);
            standardWkb[3] = (byte) ((wkbTypeWord >> 16) & 0xFF);
            standardWkb[4] = (byte) ((wkbTypeWord >> 24) & 0xFF);
        }
        System.arraycopy(coordsOnly, 0, standardWkb, 5, coordsOnly.length);

        Geometry geometry = WKB_READER.read(standardWkb);
        if (srid != 0) {
            geometry.setSRID(srid);
        }
        return geometry;
    }

    /**
     * 十六进制字符串 → 字节数组
     */
    private static byte[] hexToBytes(String hex) {
        int len = hex.length();
        byte[] bytes = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            int high = Character.digit(hex.charAt(i), 16);
            int low = Character.digit(hex.charAt(i + 1), 16);
            if (high == -1 || low == -1) {
                throw new IllegalArgumentException(
                        "Invalid hex string at position " + i + ": " + hex);
            }
            bytes[i / 2] = (byte) ((high << 4) | low);
        }
        return bytes;
    }
}

写入逻辑 :使用 JTS 的 WKBWriter,维度设为 2(X, Y),字节序使用 LITTLE_ENDIAN(PostGIS 默认),includeSRID 设为 true(写入 EWKB 格式,保留 SRID 信息)。

读取逻辑:先判断格式,如果以 POINT / LINESTRING / POLYGON / MULTI 开头,按 WKT 解析;否则按 EWKB 十六进制解析。EWKB 解析的核心步骤:读取字节序 → 读取 type word → 提取 SRID(如果有)→ 重建标准 WKB → WKBReader.read()。

5.3 Mapper 层

Mapper 接口继承 MyBatis-Plus 的 BaseMapper,获得基础 CRUD 能力:

java 复制代码
public interface PoiMapper extends BaseMapper<PoiEntity> {
}

对应的 XML 映射文件(resources/mapper/PoiMapper.xml):

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.pgd.postgisdemo.mapper.PoiMapper">
</mapper>

由于使用了 MyBatis-Plus 的自动 CRUD(getById / save / updateById / removeById),XML 中无需手写 SQL。

5.4 Service 层(业务逻辑收敛)

Service 层负责:Geometry 构造、WKT 解析、事务控制。Controller 只做 HTTP 协议转换。

服务接口

java 复制代码
public interface PoiService extends IService<PoiEntity> {
    Long addPoi(AddPoiReq req);
    boolean updatePoi(UpdatePoiReq req);
    boolean deletePoi(Long id);
    PoiDetailResp getDetailById(Long id);
}

服务实现

java 复制代码
@Service
public class PoiServiceImpl extends ServiceImpl<PoiMapper, PoiEntity>
        implements PoiService {

    private final WKTReader wktReader = new WKTReader();
    private final WKTWriter wktWriter = new WKTWriter();

    @Override
    public Long addPoi(AddPoiReq req) {
        try {
            Geometry geom = wktReader.read(req.getGeom());
            PoiEntity entity = new PoiEntity(
                req.getName(), req.getCategory(), geom);
            save(entity);
            return entity.getId();
        } catch (Exception e) {
            throw new RuntimeException("Geometry 解析失败", e);
        }
    }

    @Override
    public boolean updatePoi(UpdatePoiReq req) {
        try {
            Geometry geom = wktReader.read(req.getGeom());
            PoiEntity entity = new PoiEntity(
                req.getName(), req.getCategory(), geom);
            entity.setId(req.getId());
            return updateById(entity);
        } catch (Exception e) {
            throw new RuntimeException("Geometry 解析失败", e);
        }
    }

    @Override
    public boolean deletePoi(Long id) {
        return removeById(id);
    }

    @Override
    public PoiDetailResp getDetailById(Long id) {
        PoiEntity entity = getById(id);
        if (entity == null) return null;
        PoiDetailResp resp = new PoiDetailResp();
        resp.setId(entity.getId());
        resp.setName(entity.getName());
        resp.setCategory(entity.getCategory());
        resp.setGeom(wktWriter.write(entity.getGeom()));
        return resp;
    }
}

设计原则 :前端永远只传 WKT 字符串(如 "POINT(116.397 39.903)"),Geometry 的构造和解析全部收敛在 Service 层。Controller 不做任何空间逻辑。

5.5 Controller 层(HTTP 边界)

Controller 只负责接收请求、调用 Service、返回响应:

java 复制代码
@RestController
@RequestMapping("/poi")
public class PoiController {
    @Resource
    private PoiService poiService;

    @PostMapping("/add")
    public R<Long> add(@RequestBody AddPoiReq req) {
        return R.ok(poiService.addPoi(req));
    }

    @PostMapping("/update")
    public R<String> update(@RequestBody UpdatePoiReq req) {
        boolean ok = poiService.updatePoi(req);
        return ok ? R.ok("更新成功") : R.fail("更新失败");
    }

    @DeleteMapping("/delete/{id}")
    public R<String> delete(@PathVariable Long id) {
        boolean ok = poiService.deletePoi(id);
        return ok ? R.ok("删除成功") : R.fail("删除失败");
    }

    @GetMapping("/{id}")
    public R<PoiDetailResp> detail(@PathVariable Long id) {
        PoiDetailResp resp = poiService.getDetailById(id);
        return resp != null ? R.ok(resp) : R.fail("POI 不存在");
    }
}

六、接口测试示例

6.1 新增 POI

http 复制代码
POST /poi/add
Content-Type: application/json

{
  "name": "天安门",
  "category": "景点",
  "geom": "POINT(116.397 39.903)"
}

6.2 更新 POI

http 复制代码
POST /poi/update
Content-Type: application/json

{
  "id": 42,
  "name": "天安门广场",
  "category": "景点",
  "geom": "POINT(116.3975 39.9035)"
}

6.3 删除 POI

http 复制代码
DELETE /poi/delete/4

6.4 查询详情

http 复制代码
GET /poi/42

七、踩坑复盘(重点)

7.1 MyBatis / MyBatis-Plus 自动配置冲突

  • 问题现象 :启动时抛出 BeanDefinitionStoreException,提示 Mapper 注册冲突。
  • 根因:MyBatis-Plus 3.5.7 自带 mybatis-spring 2.x(适配 Spring Boot 2.x),与 Spring Boot 3.x 不兼容。排除低版本并显式引入 mybatis-spring 3.0.3 后解决。
  • 解决方案 --- pom.xml 中:
xml 复制代码
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.7</version>
  <exclusions>
    <exclusion>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>3.0.3</version>
</dependency>

7.2 Maven Compiler Plugin 版本问题

  • 问题现象mvn compile 失败,提示 annotationProcessorPath 的 version 不能为空。
  • 根因:maven-compiler-plugin 3.11+ 要求 annotationProcessorPaths 中的每个依赖必须显式指定版本号。Lombok 的版本为空导致编译失败。
  • 解决方案:简化 pom.xml,删除整个 maven-compiler-plugin 的自定义配置,只保留 spring-boot-maven-plugin。Spring Boot 3.2.5 会自动处理 Lombok 和 Java 17 的编译。

7.3 PostGIS JDBC 返回格式陷阱

  • 问题现象 :TypeHandler 中 rs.getBytes() 拿到的是 ASCII 字节(0x30= '0', 0x31='1'...),而不是原始二进制 WKB。
  • 根因 :PostGIS JDBC 驱动默认将 geometry 列以十六进制字符串形式返回。rs.getBytes() 拿到的是每个十六进制字符的 ASCII 编码,不是二进制数据。
  • 解决方案 :使用 rs.getString() 获取十六进制文本,再手动 hexDecode 为二进制字节数组。或使用 rs.getString() 后判断是否为 WKT 格式,直接由 WKTReader 解析。

7.4 EWKB SRID Flag 解析

  • 问题现象WKBReader.read() 抛出 ParseException: Unknown WKB type 592 / 619 / 524
  • 根因:EWKB(Extended WKB)在 type word 的 bit 29 上设置了 hasSRID 标志位(0x20000000)。直接从 EWKB 中提取坐标数据后,缺少标准 WKB 的 type word,导致 WKBReader 解析错位。
  • 解决方案:解析 EWKB 时,提取 SRID 后,将剩余坐标数据前加上标准 WKB 的 byte order + type word(清除 hasSRID 标志位),重建标准 WKB 再交给 WKBReader 解析。

7.5 TypeHandler 生效条件

  • 问题现象entity.getGeom() 始终为 null,即使配置了 @TableField(typeHandler = ...)
  • 根因 :MyBatis 中,手写 SQL + resultType 不会自动应用 @TableField 上声明的 TypeHandler。只有以下三种方式能确保 TypeHandler 生效:
    1. MP 自动 CRUD + @TableName(autoResultMap = true)
    2. 手写 resultMap 并在 <result> 中显式指定 typeHandler
    3. 使用 @TableField(value = "ST_AsBinary(geom)") 指定 SQL 表达式
  • 推荐方案 :使用 MP 自动 CRUD(getById / save / updateById / removeById)+ autoResultMap = true,最简洁、最不容易出错。

八、最佳实践总结

架构分层

  • Controller 只做 HTTP 协议转换 + 参数校验
  • Service 负责 Geometry 构造、WKT 解析、事务控制
  • 前端永远只传 WKT 字符串,不直接操作 Geometry
  • Entity 是数据库模型,DTO 是前端契约,职责分离

GeometryTypeHandler 设计原则

  • 写入:使用 WKBWriter(2, LITTLE_ENDIAN, true) 生成 EWKB
  • 读取:先判断 WKT / EWKB 格式,分别处理
  • EWKB 解析:提取 SRID → 重建标准 WKB → WKBReader.read()
  • 不依赖 PGgeometry,不依赖 PGgeometry.toString()

MyBatis-Plus 使用建议

  • 优先使用 MP 自动 CRUD,减少手写 SQL
  • Entity 上必须加 @TableName(autoResultMap = true)
  • Geometry 字段必须加 @TableField(typeHandler = GeometryTypeHandler.class)
  • 排除 MyBatis-Plus 自带的低版本 mybatis-spring,引入 3.0.x
相关推荐
我是坏垠2 小时前
Crypto、Cipher与Password:Java加密开发的三个核心概念
java·开发语言·python
white_ant2 小时前
JDK LTS 版本升级迁移注意事项大全
java·开发语言
广州灵眸科技有限公司4 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
库克克4 小时前
【C++ 】内联函数
java·开发语言·c++
zh_xuan4 小时前
java 虚拟线程
java·开发语言·虚拟线程
jason成都4 小时前
RTKLIB Java 移植踩坑复盘:卡尔曼滤波核心逻辑修复与滑坡场景实测验证
java·开发语言
老毛肚5 小时前
juc线程通信
java·开发语言·jvm
花颜yyds6 小时前
Java 开发基础速查手册
java
没钥匙的锁16 小时前
05-Java面向对象构造器与封装
java·开发语言
aaPIXa6227 小时前
C++采样引导优化SPGO——比PGO更智能的编译器决策新方案
java·c++·人工智能