Spring Boot 中使用 GraphQL 实现文件上传

1、简介 GraphQL 改变了开发人员与 API 交互的方式,为传统的 REST 方法提供了一个精简、强大的替代方案。 然而,在 Java 中使用 GraphQL 处理文件上传,特别是在 Spring Boot 应用中,由于 GraphQL 对二进制数据的处理方式,需要进行一些设置。本文将带你了解如何在 Spring Boot 应用中使用 GraphQL 上传文件。 2、GraphQL文件上传 与 HTTP 文件上传 在使用 Spring Boot 开发 GraphQL API 时,遵循最佳实践通常涉及利用标准的 HTTP 请求来处理文件上传。 通过专用的 HTTP 端点管理文件上传,然后通过 URL 或 ID 等标识符将这些上传链接到 GraphQL Mutation,开发人员可以有效地将直接嵌入 GraphQL Query 的文件上传的复杂性和处理开销降至最低。这种方法不仅简化了上传过程,还有助于避免与文件大小限制和序列化需求相关的潜在问题,从而有助于实现更加精简和可扩展的应用结构。 不过,在某些情况下,有必要将文件上传直接整合到 GraphQL 查询中。在这种情况下,需要进行一些特别的定制,在用户体验和应用程序性能之间取得平衡。因此,我们需要定义一种专门的量 Scalar Type 来处理上传。此外,这种方法还需要部署特定的机制来验证输入,并将上传的文件正确映射到 GraphQL 操作中的变量。此外,文件上传需要使用 Content Type 为 multipart/form-data 的请求体,因此需要实现一个自定义的 HttpHandler。 3、 在 GraphQL 中实现文件上传 首先,需要使用 Spring Boot 官方的 GraphQL Starter。

Spring Prototype Bean 需要手动销毁吗?

1、简介 本文将带你了解 Spring 如何处理 Prototype Bean 并管理其生命周期,主要介绍是否有必要手动销毁 Prototype Bean、何时销毁以及如何销毁。 Spring 提供了多种 Bean Scope,本文主要聚焦 Prototype。 2、Prototype Bean 及其生命周期 Scope(作用域)确定了 Bean 在其存在的上下文中的生命周期和可见性。根据 Scope 的定义 ,IoC 容器负责管理 Bean 的生命周期。Prototype(原型)Scope 指示容器在每次使用 getBean() 请求或注入到另一个 Bean 时创建一个新的 Bean 实例。在创建和初始化方面,可以放心地依赖于 Spring。然而,销毁 Bean 的过程则不同。 在了解销毁 Bean 的必要性之前,先看看如何创建一个 Prototype Bean,如下: @Component // 指定 Bean 的 Scope 为 Prototype @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeExample { } 3、Prototype Bean 需要手动销毁吗? Spring 不会自动销毁 Prototype Bean。与 Singleton Scope 不同,IoC 容器并不负责 Prototype Bean 整个的生命周期。容器会实例化、配置和组装 Prototype Bean,但之后将停止跟踪其状态。

Spring Framework v6.1.11 发布

Spring Framework v6.1.11 正式发布。 ⭐ 新特性 在 SpEL 的 ReflectionHelper 中确保 MethodHandle 的 varargs 组件类型不为 null#33193 在响应过程中出现 Reactor-Netty PrematureCloseException 时,WebClient 异常消息令人困惑 #33127 当发现无效的 factoryBeanObjectType 属性时,在异常中包含 Bean 名称 #33117 在响应式缓存切面使用 Error Handler #33073 getTypeForFactoryMethod 应捕获 NoClassDefFoundError 异常#33075 🐞 Bug 修复 SpEL 无法使用数组调用 varargs MethodHandle 函数 #33191 SpEL 无法调用变量参数为零的 varargs MethodHandle 函数 #33190 嵌套的 Bean 实例 Supplier 调用不会保留先前的工厂方法 #33180 DefaultErrorResponseBuilder 未实现 headers(Consumer) #33156 修复对 Set 方法参数的违规适配 #33150 使用 kotlinx-serialization 时,Web Controller 调用无效正文导致结果为 500,而不是 400 #33138 "file:.

ExecutorService 与 CompletableFuture 指南

1、简介 本文将带你了解 Java 中用于处理并发任务的两个重要的类:ExecutorService 和 CompletableFuture。 主要介绍它们的功能、如何有效地使用它们、以及它们之间的区别。 2、ExecutorService 概览 ExecutorService 是 Java java.util.concurrent 包中的一个功能强大的接口,可简化对需要并发运行的任务的管理。它抽象掉了线程创建、管理和调度的复杂性,让我们可以专注于需要完成的实际工作。 ExecutorService 提供了 submit() 和 execute() 等方法,用于提交我们希望并发运行的任务。然后,这些任务会进入队列并分配给线程池中的可用线程。如果任务返回结果,我们可以使用 Future 对象来检索结果。不过,使用 Future 上的 get() 等方法检索结果会阻塞调用线程,直到任务完成。 3、CompletableFuture 概览 CompletableFuture 是在 Java 8 中引入的。它专注于以更声明式的方式组合异步操作并处理它们的最终结果。CompletableFuture 充当一个容器,保存异步操作的最终结果。它可能不会立即返回结果,但提供了方法来定义在结果可用时要执行的操作。 ExecutorService 在检索结果时会阻塞线程,而 CompletableFuture 则以非阻塞方式运行。 4、关注点和职责 虽然 ExecutorService 和 CompletableFuture 都能解决 Java 中的异步编程问题,但它们的各自的关注点和职责却截然不同。 4.1、ExecutorService ExecutorService 专注于管理线程池和并发执行任务。它提供了创建具有不同配置(如固定大小、缓存和定时调度)的线程池的方法。 来看一个使用 ExecutorService 创建并维护三个线程的示例,如下: ExecutorService executor = Executors.newFixedThreadPool(3); Future<Integer> future = executor.submit(() -> { // 任务执行逻辑 return 42; }); 调用 newFixedThreadPool(3) 方法会创建一个包含三个线程的线程池,可以确保同时执行的任务不会超过三个。然后使用 submit() 方法提交任务供线程池执行,并返回一个代表计算结果的 Future 对象。

自定义 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.