Protobuf 和 gRPC

1、概览 在软件开发中,微服务架构已成为创建可扩展和可维护系统的一种受欢迎的方法。在微服务之间进行有效的通信至关重要,其中涉及的技术包括 REST、消息队列、Protocol Buffer(Protobuf)和 gRPC等。 本文将重点介绍 Protobuf 和 gRPC,研究它们的异同、优缺点,从而全面了解它们在微服务架构中的作用。 2、Protobuf Protocol Buffer 是一种不依赖语言和平台的结构化数据序列化和反序列化机制。其创建者谷歌宣称,与 XML 和 JSON 等其他类型的 Payload 相比,Protocol Buffer 更快、更小、更简单。 Protobuf 使用 .proto 文件来定义数据的结构。每个文件都描述了可能从一个节点传输到另一个节点或存储在数据源中的数据。定义了 Schema 后,就可以使用 Protobuf 编译器(protoc)生成各种语言的源代码: syntax = "proto3" message Person { string name = 1; int32 id = 2; string email = 3; } 这是一个有三个字段的 Person 类型的简单消息协议。name 和 email 是 String 类型,而 id 是整数类型。 2.1、Protobuf 的优势 来看看使用 Protobuf 的一些优势。 Protobuf 数据结构紧凑,易于序列化和反序列化,因此在速度和存储方面都非常高效。 Protobuf 支持多种编程语言,如 Java、C++、Python、Go 等,促进了无缝跨平台数据交换。 它还能在不中断已部署程序的情况下添加或删除数据结构中的字段,从而实现无缝版本控制和更新。

Spring AI 结构化输出

1、简介 本文将带你了解如何格式化 Spring AI 的输出结构,使其更易于使用且更加直观。 2、聊天模型简介 ChatModel 接口是向 AI 模型发出提示的基本结构: public interface ChatModel extends Model<Prompt, ChatResponse> { default String call(String message) { // 忽略实现。。。 } @Override ChatResponse call(Prompt prompt); } call() 方法的作用是向模型发送消息并接收响应,仅此而已。 自然而然地,我们期望提示和响应是 String 类型。然而,现代模型的实现通常具有更复杂的结构,可以进行更精细的调整,提高模型的可预测性。例如,虽然可用的默认 call() 方法接受 String 参数,但更实用的做法是使用 Prompt。Prompt 可以包含多个消息或包括诸如 “温度” 之类的选项,以调节模型的表现力。 我们可以自动装配 ChatModel 并直接调用它。例如,如果我们的依赖中有用于 OpenAI API 的 spring-ai-openai-spring-boot-starter,那么就会自动注入 OpenAI 的实现 OpenAiChatModel。 3、结构化输出 API 要获得数据结构化的输出,Spring AI 提供了使用结构化输出 API 封装 ChatModel 调用的工具。此 API 的核心接口是 StructuredOutputConverter(结构化输出转换器): public interface StructuredOutputConverter<T> extends Converter<String, T>, FormatProvider {} 它结合了另外两个接口,第一个是 FormatProvider:

解决异常 UnsupportedTemporalTypeException: Unsupported Field: InstantSeconds

1、概览 在处理日期时,我们经常使用 Date/Time API。但是,如果操作不当,操作或访问时间数据可能会导致错误和异常。其中一个经常遇到的异常就是 UnsupportedTemporalTypeException: “Unsupported Field: InstantSeconds”,通常表示指定的 Temporal(时态)对象不支持 InstantSeconds 字段 。 本将带你了解出现 UnsupportedTemporalTypeException 异常的原因,以及解决办法。 2、实例 在介绍解决方案之前,先通过一个实际例子来了解导致该异常的根本原因。 根据文档,UnsupportedTemporalTypeException 表示不支持 ChronoField 或 ChronoUnit。换句话说,当在不支持特定字段的时间(Temporal)对象上使用不支持的字段时,就会引发此异常。 异常堆栈中的 “Unsupported Field: InstantSeconds” 消息说明了一切。它表明字段 InstantSeconds 出了问题,该字段表示从纪元(Epoch)开始的秒数的连续计数的概念。 简而言之,并非所有 Date / Time API 提供的时间对象都支持该字段。例如,尝试将涉及 InstantSeconds 的操作应用到 LocalDateTime、LocalDate 和 LocalTime 时,会导致 UnsupportedTemporalTypeException 异常。 现在,来看看如何重现异常。如下,尝试把 LocalDateTime 转换为 Instant: @Test void givenLocalDateTime_whenConvertingToInstant_thenThrowException() { assertThatThrownBy(() -> { LocalDateTime localDateTime = LocalDateTime.now(); long seconds = localDateTime.getLong(ChronoField.INSTANT_SECONDS); Instant instant = Instant.ofEpochSecond(seconds); }).isInstanceOf(UnsupportedTemporalTypeException.class) .hasMessage("Unsupported field: InstantSeconds"); } 上述测试代码会失败,因为我们试图使用 LocalDateTime 实例来访问 ChronoField.

使用 Podman Desktop 容器化 Spring Boot 应用

1、概览 本文将带你了解如何使用 Podman Desktop 对 Spring Boot 应用进行容器化。Podman 是一种容器化工具,它允许我们在不需要守护进程的情况下管理容器。 Podman Desktop 是一款具有图形用户界面(GUI)的桌面应用,用于使用 Podman 管理容器。 为了演示其用法,我们要创建一个简单的 Spring Boot 应用,构建容器镜像,并使用 Podman Desktop 运行容器。 2、安装 Podman Desktop 我们需要在本地计算机上 安装 Podman Desktop 才能开始使用。它适用于 Windows、macOS 和 Linux 操作系统。下载安装程序后,按照安装说明在机器上安装 Podman Desktop 即可。 以下是设置 Podman Desktop 的几个重要步骤: 机器上应已安装 Podman。如果没有安装,Podman Desktop 会提示并为我们安装。 Podman 准备就绪后,系统会提示我们启动 Podman 虚拟机。我们可以选择默认设置,也可以根据需要自定义设置。在运行容器之前,这是必须的。 此外,对于 Windows,需要启用/安装 WSL2(Windows Subsystem for Linux),然后才能运行 Podman。 在安装过程结束时,我们应该有一个正在运行的 Podman 虚拟机,并可以使用 Podman Desktop 进行管理。 可以在 “Dashboard”(仪表盘)部分看到: 3、创建 Spring Boot 应用 创建一个小型 Spring Boot 应用。该应用有一个 REST Controller,当我们访问 /hello 端点时,它返回一条 "Hello, World!

Spring Boot 测试 CORS 跨域配置

1、简介 跨源资源共享(Cross-Origin Resource Sharing,CORS)是一种安全机制,允许网页从一个源访问另一个源的资源。它由浏览器强制执行,以防止网站向不同域发出未经授权的请求。 在使用 Spring Boot 构建 Web 应用时,必须正确测试 CORS 配置,以确保应用能安全地与授权的源交互,同时阻止未经授权的源。 通常情况下,我们只有在应用部署后才会发现 CORS 问题。通过尽早测试 CORS 配置,可以在开发过程中发现并解决这些问题,从而节省时间和精力。 本文将带你了解讨如何使用 MockMvc 编写有效的测试来验证 CORS 配置。 关于 Spring Boot 中 CORS 跨域配置的详细内容,你可以参阅 “在 Spring 应用中处理 CORS 跨域” 和 “Spring 和 CORS 跨域” 这两篇文章。 2、Spring Boot 配置 CORS 在 Spring Boot 应用中配置 CORS 有多种方法。在本文中,我们使用 Spring Security 并自定义 CorsConfigurationSource: private CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.setAllowedOrigins(List.of("https://baeldung.com")); corsConfiguration.setAllowedMethods(List.of("GET")); corsConfiguration.setAllowedHeaders(List.of("X-Baeldung-Key")); corsConfiguration.setExposedHeaders(List.of("X-Rate-Limit-Remaining")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.

解决异常 PSQLException: Operator Does Not Exist: Character Varying = UUID

1、简介 本文将带你了解 JPA 与 PostgreSQL 交互时出现 PSQLException 异常:“Operator Does Not Exist: Character Varying = UUID” 的原因,以及如何处理和避免该异常。 2、异常原因 PostgreSQL 区分 Character Varying(字符串)和 UUID 数据类型。这种区分要求在这些类型之间进行比较时进行 显式类型转换。因此,当我们尝试直接将 UUID 值与字符串(VARCHAR)列进行比较时,PostgreSQL 会抛出异常,因为它缺乏用于这种特定类型比较的操作符。 举个例子,有一个 User 实体,其 varchar 列为 uuid: @Entity @Table(name = "User_Tbl") public class User{ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(columnDefinition = "varchar") private UUID uuid; // Gette / Setter 方法省略 } 当尝试使用 UUID 值查询数据库时,会出现异常: // UUID UUID testId = UUID.fromString("c3917b5b-18ed-4a84-a6f7-6be7a8c21d66"); User user = new User(); user.

Java 枚举、JPA 和 PostgreSQL 枚举

1、简介 本文将带你了解 Java 枚举、JPA 和 PostgreSQL 枚举的概念,以及如何将它们结合使用,在 Java 枚举和 PostgreSQL 枚举之间创建无缝映射。 2、Java 枚举 Java 枚举(Enum)是一种特殊类型的类,用于表示一组固定数量的常量。枚举用于定义一组具有底层类型(如字符串或整数)的命名值。当我们需要定义一组在应用中具有特定含义的命名值时,枚举非常有用。 下面是一个 Java 枚举的示例: public enum OrderStatus { PENDING, IN_PROGRESS, COMPLETED, CANCELED } 在本例中,OrderStatus 枚举定义了四个常量。这些常量可以在我们的应用中用来表示订单的状态。 3、使用 @Enumerated 注解 在 JPA 中使用 Java 枚举时,需要用 @Enumerated 来注解枚举字段,以指定如何将枚举值存储到数据库中。 首先,定义一个名为 CustomerOrder 的实体类,并用 @Entity 进行注解,以标记其用于 JPA 持久化: @Entity public class CustomerOrder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Enumerated() private OrderStatus status; // 其他的字段和方法省略 } 默认情况下,JPA 将枚举值存储为整数(Integer),代表枚举常量的序号位置(ordinal())。例如,上文中的 OrderStatus 枚举值 PENDING、IN_PROGRESS、COMPLETED 和 CANCELED,默认行为将分别把它们存储为整数 0、1、2 和 3。由此产生的数据库表将有一个小 int 类型的状态列,其值为 0 至 3:

在 Spring 中使用 Logbook 记录 HTTP 请求和响应

1、概览 Logbook 是一个可扩展的 Java 库,可为不同的客户端和服务器端提供完整的请求和响应日志。它允许开发人员记录应用接收或发送的任何 HTTP 流量。这可用于日志分析、审计或分析流量问题。 本文将带了解如何在 Spring Boot 中整合 Logbook,以及如何使用 Logbook 记录 HTTP 请求和响应。 2、依赖 在 Spring Boot 中添加 logbook-spring-boot-starter 依赖: <dependency> <groupId>org.zalando</groupId> <artifactId>logbook-spring-boot-starter</artifactId> <version>3.9.0</version> </dependency> 你可以在 Maven Central 中找到最新版本的 Logbook。 3、配置 Logbook 与 Spring Boot 应用中的 logback 日志配合使用。 我们需要在 logback-spring.xml 和 application.properties 文件中添加配置。 在 pom.xml 中添加 Logbook 库后,Spring Boot 就会自动配置 Logbook 库,我们需要做的就是在 application.properties 文件中添加日志级别: logging.level.org.zalando.logbook.Logbook=TRACE 启用 TRACE 日志级别,即可记录 HTTP 请求和响应。 此外,还要在 logback-spring.xml 文件中添加了 Logbook 配置: <logger name="org.zalando.logbook" level="INFO" additivity="false"> <appender-ref ref="RollingFile"/> </logger> 添加完成后,就可运行应用了。每次 HTTP 请求调用后,Logbook 都会将请求和响应记录到 logback-spring.

Spring Data JPA 检索最大值(Max Value)

1、简介 本文将带你了解如何使用 Spring Data JPA 检索数据列中的最大值(Max Value)。 2、示例 首先,添加 spring-boot-starter-data-jpa 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 然后,定义一个简单的 Employee 实体表示员工: @Entity public class Employee { @Id @GeneratedValue private Integer id; private String name; private Long salary; // 工资 // 构造函数、Getter、Setter 方法省略 } 接下来,看看有哪些方法可以检索出所有员工中薪水(salary)列的最大值。 3、使用 Repository 的派生查询 Spring Data JPA 提供了一个强大的机制,可以使用 Repository 方法来定义自定义查询。其中之一是派生查询,它允许我们通过声明方法名来实现SQL查询。 创建 Employee 实体类的 Repository 接口: public interface EmployeeRepository extends JpaRepository<Employee, Integer> { Optional<Employee> findTopByOrderBySalaryDesc(); } 如上,我们实现了一个方法 findTopByOrderBySalaryDesc,该方法使用查询派生机制生成相应的 SQL。根据方法名,它将按照工资(salary)降序对所有员工进行排序,然后返回第一个员工,即工资最高的员工。 该方法会返回一个加载了所有属性的实体。如果我们只想检索一个工资(salary)值,可以使用投影查询: 创建 EmployeeSalary 投影接口:

在 Golang 中实现类似于 Spring 中的模板事务

事务(TRANSACTION),是指一组操作的集合,这些操作要么全部成功,要么全部失败。其目的是在出现错误、系统崩溃或其他意外情况下,保证数据的一致性和完整性。 事务通常具有以下四个重要的特性,这些特性被统称为 ACID 属性: Atomicity(原子性): 定义: 事务中的所有操作要么全部完成,要么全部不完成,任何一个操作失败都会导致整个事务的失败,并且事务的所有操作都会被回滚(撤销)。 示例: 银行转账操作,如果从一个账户扣款后无法在另一个账户中存款,那么整个操作将回滚,不会执行任何更改。 Consistency(一致性): 定义: 事务只能把数据库从一种一致状态转换到另一种一致状态。在事务开始之前和结束之后,数据库的完整性约束没有被破坏。 示例: 在一个事务中插入数据时,如果插入的数据违反了数据库的完整性约束(例如唯一约束),那么这个事务将失败,数据库将保持一致状态。 Isolation(隔离性): 定义: 事务的执行是隔离的,多个事务并发执行时,一个事务的执行不会受到其他事务的干扰。隔离性确保了并发事务的执行结果与按顺序执行的结果相同。 示例: 两个用户同时购买同一件商品,隔离性确保每个用户看到的库存是正确的,避免超卖的情况。 Durability(持久性): 定义: 一旦事务提交,其结果将永久保存在数据库中,即使系统崩溃也不会丢失。 示例: 即使在事务提交后立即发生系统崩溃,事务的结果也会保存在数据库中,重启系统后数据依然存在。 以 MYSQL 关系型数据库为例,事务的使用如下: -- 开始事务 BEGIN TRANSACTION; -- TODO 执行业务 1 -- TODO 执行业务 2 -- TODO 执行业务 3 -- .... -- 提交事务 COMMIT; -- 或者,回滚事务 ROLLBACK; 其中,BEGIN TRANSACTION、COMMIT 以及 ROLLBACK 都是事务固定的模板代码,当代的大多数框架都会自动帮我们进行处理。 Spring 对事务的支持 Spring 对关系型数据库中的事务提供强大的支持,包括声明式事务、TransactionTemplate 模板事务等等。 @Transactional 声明式事务 实际开发中,最常用的就是通过 @Transactional 注解来声明事务方法。事务方法会在执行开始前自动开始事务,在方法结束后自动提交事务,在执行过程中如果遇到异常则自动回滚事务。 import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional // 为 public 方法开启事务 public class FooService { public void service () { // TODO 在事务中执行业务 } } 在类上注解 @Transactional,则会为当前类中所有 public 方法开启声明式事务。也可以单独注解在方法上,则会覆盖类上的 @Transactional 定义。