教程

自定义 ExecutorService 中线程的名称

1、概览 ExecutorService 在 Java 中提供了一种方便的方式来管理线程并执行并发任务。在使用 ExecutorService 时,为线程和线程池分配有意义的名称可以提高调试、监控和理解线程的效果。 本文将带你了解在 Java 的 ExecutorService 中为线程和线程池命名的不同方式。 2、设置线程的名称 如果不使用 ExecutorService,可以在 Java 中通过 Thread#setName 方法轻松设置线程名。 ExecutorService 使用默认的线程池和线程名称,如 pool-1-thread-1、pool-1-thread-2 等,但也可以为 ExecutorService 管理的线程指定自定义的线程名称。 首先,创建一个简单程序,执行 ExecuterService,输出默认的线程和线程池名称: ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executorService.execute(() -> System.out.println(Thread.currentThread().getName())); } 运行程序,输出如下: pool-1-thread-1 pool-1-thread-2 pool-1-thread-1 pool-1-thread-3 pool-1-thread-2 2.1、使用自定义 ThreadFactory 在 ExecutorService 中,新线程是通过 ThreadFactory 创建的。ExecutorService 默认使用 Executors.defaultThreadFactory 创建线程来执行任务。 通过向 ExecuterService 提供不同的自定义 ThreadFactory,我们可以更改线程的名称、优先级等。 首先,创建 ThreadFactory 的实现:MyThreadFactory。然后,为 MyThreadFactory 创建的新线程设置自定义线程名称:

解决 JPA 中的 PostgreSQL JSON 类型不匹配异常

1、简介 本文将带你了解在使用 JPA 与 PostgreSQL 交互时出现异常 “PSQLException error: column is of type json but the expression is of type character varying” 的原因,以及解决办法。 2、常见的原因 在 PostgreSQL 中,JSON 或 JSONB 数据类型用于存储 JSON 数据。但是,如果我们试图将字符串(character varyin)插入到期望使用 JSON 的列中,PostgreSQL 就会抛出 “column is of type json but expression is of type character varying” 错误。这种情况在使用 JPA 和 PostgreSQL 时尤其常见,因为 JPA 可能会尝试将字符串保存到 JSON 列,从而导致此错误。 3、异常演示 创建一个基本的 Spring Boot 项目,在 Maven pom.xml 文件中添加 PostgreSQL 依赖: <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.1</version> <scope>runtime</scope> </dependency> 创建一个映射到 student 表的 JPA 实体类:

Hibernate 异常 UnknownEntityException:Could not resolve root entity

1、概览 本文将带你了解 Hibernate 出现异常 “UnknownEntityException:Could not resolve root entity” 的原因,以及解决办法。 2、理解异常 通常,在 HQL 或 JPQL 查询中解析已知映射实体名称失败时,Hibernate 就会抛出 “UnknownEntityException: Could not resolve root entity” 异常。 简而言之,Hibernate 依赖 JPA 实体来完成对象关系映射的所有工作。因此,它希望查询中指定的实体名称与 @Entity 注解所注解的类名称相匹配。 因此,导致异常的最常见原因之一就是 使用了与有效实体类名称不匹配的名称。 3、示例 知道了导致 Hibernate 出现 UnknownEntityException 的原因后,来看看如何在实践中重现这种情况。 首先,创建一个 Person 类,如下: @Entity public class Person { @Id private int id; private String firstName; private String lastName; // 标准 Getter / Setter 方法省略 } 如上,使用 id、firstName 和 lastName 来定义一个 Person。 @Entity 注解表示 Person 类是一个 JPA 实体,@Id 表示代表主键的字段。

JDBC INSERT 返回自增 Id

1、简介 在使用 JDBC 向数据库插入数据时,如果主键 ID 是自增的,那么我们需要获取到新插入的这条数据的 ID。JDBC 提供了一种在 INSERT 操作后立即获取其自增 ID 的机制。 2、示例项目 为了方便测试,本例使用H2 内存数据库。 在 pom.xml 文件中添加 h2 数据库依赖: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.1.214</version> </dependency> 首先,连接到 H2 数据库,并在数据库中创建 EMPLOYEES 测试表: private static void populateDB() throws SQLException { String createTable = """ CREATE TABLE EMPLOYEES ( id SERIAL PRIMARY KEY , first_name VARCHAR(50), last_name VARCHAR(50), salary DECIMAL(10, 2) ); """; PreparedStatement preparedStatement = connection.prepareStatement(createTable); preparedStatement.execute(); } 3、检索自增 ID 执行 INSERT 语句时,如果表有自动生成的键(如 MySQL 中的 AUTO_INCREMENT、PostgreSQL 中的 SERIAL 或 H2 数据库中的 IDENTITY),JDBC 可以使用 getGeneratedKeys() 方法检索这些键。

Java 中的 getResourceAsStream() 和 FileInputStream

1、概览 本文将带你了解 Java 中读取文件的不同方法之间的差异。主要介绍 getResourceAsStream() 方法和 FileInputStream 类,以及它们的用例。 先说结论,Files.newInputStream() 方法,由于其在内存和性能方面的好处,推荐用于替代 FileInputStream。 2、基础知识 首先来了解一下 getResourceAsStream() 和 FileInputStream 之间的区别以及它们的常见用例。 2.1、使用 getResourceAsStream() 读取文件 getResourceAsStream() 方法从 classpath 读取文件。传递给 getResourceAsStream() 方法的文件路径应相对于 classpath。该方法返回一个可用于读取文件的 InputStream。 这种方法通常用于读取配置文件、properties 文件和其他与应用打包在一起的资源。 2.2、使用 FileInputStream 读取文件 FileInputStream 类用于从文件系统中读取文件。当需要读取未与应用打包在一起的文件时(本地磁盘),该类非常有用。 传递给 FileInputStream 构造函数的文件路径应该是绝对路径或与当前工作目录相对的路径。 FileInputStream 对象由于使用了 finalizers(finalize() 方法),可能存在内存和性能问题。FileInputStream 的更好替代方案是 Files.newInputStream() 方法,其工作方式相同。 本文示例中使用 Files.newInputStream() 方法从文件系统中读取文件。 这些方法通常用于读取文件系统中的外部文件,如日志文件、用户数据文件和 Secret 文件。 3、代码示例 让我们通过一个示例来演示 getResourceAsStream() 和 Files.newInputStream() 的用法。 创建一个简单的工具类,使用这两种方法读取文件。然后,通过从 classpath 和文件系统中读取示例文件来测试这两种方法。 3.1、使用 getResourceAsStream() 首先,来看看 getResourceAsStream() 方法的用法。 创建一个名为 FileIOUtil 的类,并添加一个从指定资源中读取文件的方法: static String readFileFromResource(String filePath) { try (InputStream inputStream = FileIOUtil.

使用 Redis 和 Spring AI 创建 RAG(检索增强生成)应用

1、概览 在本教程中,我们将使用 Spring AI 框架和 RAG(检索增强生成)技术构建一个 ChatBot(聊天机器人)。在 Spring AI 的加持下,我们将与 Redis Vector(向量)数据库集成,以存储和检索数据,从而增强 LLM(大型语言模型)的提示功能。一旦 LLM 接收到包含相关数据的提示,它就会有效地用自然语言生成带有最新数据的响应,以回应用户的查询。 2、RAG 是什么? LLM 是根据互联网上的大量数据集预先训练的机器学习模型。要使 LLM 在私营企业中发挥作用,我们必须根据特定组织的知识库对其进行微调。然而,微调通常是一个耗时的过程,需要大量的计算资源。此外,经过微调的 LLM 很有可能会对查询生成不相关或误导性的响应。这种行为通常被称为 LLM 幻觉(LLM Hallucinations)。 在这种情况下,RAG 是一种优秀的技术,用于限制或将 LLM 的响应置于特定上下文中。向量数据库在 RAG 架构中发挥着重要作用,为 LLM 提供上下文信息。但是,在 RAG 架构中使用矢量(向量)数据库之前,应用必须通过 ETL(提取、转换和加载)流程对其进行填充: Reader 从不同源检索组织的知识库文档。然后,Transformer(转换器)将检索到的文档分割成小块,并使用嵌入模型对内容进行矢量化。最后,Writer 将向量或 Embedding 加载到向量数据库。向量数据库是专门的数据库,可以在多维空间中存储这些 Embedding。 在 RAG 中,如果矢量数据库定期从组织的知识库中更新,那么 LLM 就能对几乎实时的数据做出响应。 一旦矢量数据库中的数据准备就绪,应用就可以使用它来检索用户查询的上下文数据: 应用将用户查询与矢量数据库中的上下文数据相结合形成提示,最后将其发送给 LLM。LLM 在上下文数据的范围内用自然语言生成回复,并将其发送回应用。 3、使用 Spring AI 和 Redis 实现 RAG Redis Stack 提供矢量搜索服务,我们将使用 Spring AI 框架与之集成,并构建一个基于 RAG 的 ChatBot(聊天机器人)应用。此外,我们还要使用 OpenAI 的 GPT-3.

Java PreparedStatement 插入 JSON 对象到 PostgreSQL

1、简介 在现代软件开发中,由于 JSON 数据的轻量和通用性,处理 JSON 数据已经变得无处不在。PostgreSQL 凭借其对 JSON 的强大支持,为存储和查询 JSON 数据提供了出色的平台。 在 Java 中,我们通常使用 JDBC 与数据库进行交互,本文将带你了解如何使用 Java 的 PreparedStatement 将 JSON 对象插入 PostgreSQL 数据库。 2、依赖 首先,需要设置环境。除了安装和运行 PostgreSQL,还需要在项目的依赖中添加 PostgreSQL JDBC 驱动和 org.json 库。 2.1、安装和运行 PostgreSQL 如果你还没有安装 PostgreSQL,可以从 PostgreSQL 官方网站 下载并安装。PostgreSQL 支持 JSON 已经有相当长的时间了,你可以选择从 PostgreSQL 9 开始的任何版本。本文使用最新的稳定版本,即 PostgreSQL 16。 在继续阅读之前,你需要确保 PostgreSQL 正常运行,并可通过必要的凭据访问。 2.2、添加 PostgreSQL JDBC 驱动 将 PostgreSQL JDBC 驱动添加到项目的依赖中。 对于 Maven 项目,可以在 pom.xml 中添加如下 依赖: <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.3</version> </dependency> 2.3、添加 JSON 依赖 要在 Java 代码中处理 JSON 数据,还需要添加 JSON 库依赖。目前有几种流行的 Java JSON 库,如 Jackson、Gson、FastJson 和 org.

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.