Allin Program

记 Find Somebody Backend 升级

出于某些原因,服务器数据库需要迁移至 PostgreSQL,顺带把Find Somebody Backend也进行技术升级了。

主要技术栈变动如下:

过去 现在
Maven 3 Gradle 8
Java 11 Java 17
Spring Boot 2 Spring Boot 3
Spring MVC Spring WebFlux
MySQL 8 PostgreSQL 16.04
JVM Native Image

Maven -> Gradle

这还是挺顺利的。

说来令人笑话,玩Android开发快十年纯学会安装 Gradle 了。

Java 11 -> Java 17

常见语法都兼容,也没啥问题。

Spring Boot 2 -> Spring Boot 3

这还真遇到点问题了,需要上网检索。

找不到 @PostConstruct

Spring Boot 3 迁移到 Jakarta EE 9,@PostConstructjavax.annotation 移动到了 jakarta.annotation

这在 Hibernate 高版本中也是一样的,其注解早已放在 jakarta 包下。

改名相关:

  1. https://eclipse-foundation.blog/2018/02/26/and-the-name-is/
  2. https://www.eclipse.org/ee4j/faq.php

Spring MVC ->Spring WebFlux

此前对 Spring WebFlux 仅限于 WebClient 的使用,本次有了更多的接触。

弃用 HttpServletRequest

若平时使用 HttpServletRequest获取请求头信息,而不是 @RequestHeader 。那么区别于 Spring MVC,现在得这么写:

public static String getClientIpAddress(ServerWebExchange exchange) {
    ServerHttpRequest request = exchange.getRequest();
  
    // 先检查 X-Forwarded-For 头
    String xForwardedFor = request.getHeaders().getFirst("X-Forwarded-For");
    if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
        return xForwardedFor.split(",")[0];
    }

    // 再检查 X-Real-IP 头
    String xRealIp = request.getHeaders().getFirst("X-Real-IP");
    if (xRealIp != null && !xRealIp.isEmpty()) {
        return xRealIp;
    }

    // 否则直接获取远程地址
    return request.getRemoteAddress() != null
        ? request.getRemoteAddress().getAddress().getHostAddress()
        : "Unknown";
}

如果用不到响应信息或者上下文,也可以使用 ServerHttpRequest

HTTP接口返回 Mono<T>

WebFlux 是基于 Reactor 库构建的,支持异步和非阻塞的 I/O 操作。如果直接返回 String,WebFlux 无法知道这个操作是同步还是异步的。返回 Mono<String> 让 WebFlux 知道这是一个异步操作,能够在请求完成时再返回结果。

@GetMapping("/get-token")
public Mono<String> getToken(ServerHttpRequest request) {
    return Mono.just("token");
}

WebFlux 的目标是处理高并发请求,传统的阻塞模型无法充分利用 CPU 资源。而返回 Mono<T> 可以让请求处理异步化,避免线程等待 I/O 操作完成,提高性能。在 WebFlux 中,最好遵循响应式编程的模式,返回 Mono<T>Flux<T>,以便 WebFlux 框架能够管理异步请求和响应。

对于返回 Mono 的函数调用,建议使用 map (没有阻塞任务)/flatMap (有阻塞任务) 进行操作返回值,用法点像Dart中的 then 函数。在响应式链路中,值得一提的是,尽量不要存在 Mono 的阻塞操作,否则可能会引发运行时异常,还是挺头疼的。

MySQL 8 -> PostgreSQL 16.04

数据迁移

建议使用 pgloader ,简单迁移任务比datax好用不少。

一行命令搞定:

pgloader mysql://[user:password]@[host:port]/[db] postgresql://[user:password]@[host:port]/[db] 

在迁移中遇到了一些小问题,比较值得记录的是,pgloader不支持MySQL8最新的鉴权模式,需要降级为老的鉴权插件,可以看这里:https://github.com/dimitri/pgloader/issues/782 。

scheme

PGSQL中有这么一个概念—— scheme

对比MySQL来说,像是在库和表之间还加了一层空间隔离,名为 scheme

在使用中,若没有隔离需求,可以直接把表建在名为 public 的默认 scheme 中。那么在使用上就和MySQL是一样的。若自行创建了新的 scheme ,那么在使用上就需要像这样:

-- 显式指定scheme名称
SELECT * FROM scheme_name.table_name;

官方文档:https://www.postgresql.org/docs/current/ddl-schemas.html

JVM -> Native Image

官方文档写的不错,直接按部就班就好:https://docs.spring.io/spring-boot/how-to/native-image/developing-your-first-application.html

编译本地原生程序可使用Spring推荐的JDK套件:https://bell-sw.com/pages/downloads/native-image-kit/

我遇到的问题是 fastjson2 作妖,折腾好久还是换到了Jackson以解决问题,好在只是使用 toJsonString(),迁移成本不大。