Spring 异常 “No Multipart Boundary Was Found”

1、简介 本文将带你了解在 Spring 中处理文件上传(Multipart)请求时出现异常:“No Multipart Boundary Was Found” 的原因,以及解决办法。 2、理解 Multipart 请求 简而言之,Multipart 请求是一种 HTTP 请求,它在一条消息的请求体中传输一种或多种不同的数据。请求体被分成多个部分,请求中的每个部分都可能代表不同的文件或数据。 通常使用它来传输或上传文件、交换电子邮件、流媒体或提交 HTML 表单,并使用 Content-Type 标头来指明在请求中发送的数据类型。 来看看 Multipart 请求需要设置哪些值。 2.1、主类型 主类型(顶级类型)指定了我们发送的内容的主要类别。如果我们在单个 HTTP 请求中提交多种数据类型,则需要将 Content-Type Header 值设置为 multipart。 2.2、子类型 除了顶级类型外,Content-Type Header 值还包含一个强制的子类型。子类型值提供了有关数据格式的附加信息。 在不同的 RFC 中定义了多种 multipart 子类型。例如 multipart/mixed、multipart/alternative、multipart/related 和 multipart/form-data(常用)。 由于我们在一个请求中封装了多种不同的数据类型,因此需要一个额外的参数来分隔 multipart 消息的不同部分:即,boundary 参数。 2.3、Boundary 参数 Boundary 指令(参数)是 multipart Content-Type 的强制值,它指定了封装边界。 根据 RFC 1341 的定义,封装(Boundary)边界是由两个连字符(“–”)后跟 Content-Type Header 中的 boundary 值组成的分隔线。它用于分隔 HTTP 请求体中的各个部分(即,子请求体)。 来看一个实际的案例,Web 浏览器请求包含两个 Part。通常情况下,Content-Type Header 信息如下:

在 Spring 中实现 Bulk 和 Batch API

1、概览 尽管标准的 REST API 可以满足大多数常见需求,但在处理批量(Bulk)或批处理(Batch)操作时,基于 REST 的架构风格存在一些限制。 本文将带你了解如何在微服务中应用 Bulk 和 Batch 操作,以及如何实现一些自定义的面向 “写” 的 Bulk 和 Batch API。 2、 Bulk 和 Batch API 简介 Bulk 和 Batch 操作这两个术语经常被互换使用。不过,两者之间还是有硬性区别的。 通常,Bulk(批量)操作是指对同一类型的多个条目执行相同的操作。一种简单的方法是为每个请求调用相同的 API 来执行批量操作。这种方法可能太慢,而且会浪费资源。相反,我们可以在一次往返中处理多个条目。 我们可以通过在一次调用中对同一类型的多个条目执行相同的操作来实现 Bulk(批量)操作。这种对条目集合进行操作的方式可减少整体延迟并提高应用性能。要实现 Bulk(批量)操作,我们可以重用用于于单个条目的现有端点,或者为 Bulk(批量)方法创建一个单独的路由。 Batch(批处理)操作通常是指对多个资源类型执行不同的操作。Batch API 是在一次调用中对多个资源执行多个操作的集合。这些资源操作可能没有任何连贯性。每个请求路由可以是独立的,互相之间没有依赖关系。 简而言之,“Batch” 一词是指批量处理不同的请求。 目前,对于实现 Bulk 或 Batch 操作来说,缺乏明确定义的标准或规范。此外,许多流行的框架,如 Spring,没有内置的 Bulk(批量)操作支持。 尽管如此,在本文中,我们将使用常见的 REST 架构来实现自定义 Bulk 和 Batch 操作的方法。 3、Spring 示例应用 假设我们需要构建一个简单的微服务,同时支持 Bulk 和 Batch 操作。 3.1、Maven 依赖 首先,添加 spring-boot-starter-web 和 spring-boot-starter-validation 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.

使用 Embedding 模型和向量数据库的 Spring AI RAG

本文主要介绍以下内容: 嵌入式模型简介。 使用 DocumentReader 加载数据。 在 VectorStore 中存储 Embedding。 实现 RAG(Retrieval-Augmented Generation,检索增强生成),又名 Prompt Stuffing。 你可以在 GitHub 中找到本文的示例代码 大型语言模型(LLM),如 OpenAI、Azure Open AI、Google Vertex 等,都是在大型数据集上训练出来的。但这些模型并不是在你的私人数据上训练出来的,因此它们可能无法回答你所在领域的特定问题。但是,在你的私人数据上训练模型可能既昂贵又耗时。那么,我们该如何使用这些 LLM 来回答我们领域的特定问题呢? 其中一种方法是使用 RAG(Retrieval-Augmented Generation,检索增强生成),又称 Prompt Stuffing(提示填充)。使用 RAG,从数据存储中检索相关文档,并将其传递给 LLM 以生成答案。在这一过程中,使用嵌入模型将文档转换为 Embedding,并将其存储到向量数据库中。 了解检索增强生成(RAG) 你可能在关系数据库中存储结构化数据,在 NoSQL 数据库中存储非结构化数据,甚至在文件中存储结构化数据。你能够使用 SQL 有效地查询关系数据库,使用 NoSQL 数据库的查询语言有效地查询 NoSQL 数据库。你还可以使用 Elasticsearch、Solr 等全文搜索引擎来查询非结构化数据。 不过,你可能希望使用具有语义的自然语言检索数据。 例如,“我喜欢 Java 编程语言” 和 “Java 始终是我的首选语言” 具有相同的语义,但使用了不同的词语。尝试使用准确的词语检索数据可能不会有效。 这就是 Embedding 的作用所在。Embedding 是单词、句子或文档的向量表示。你可以通过这些 Embedding,使用自然语言检索数据。 你可以将结构化和非结构化数据转换为 Embedding,并将其存储在向量数据库中。然后,你可以使用自然语言查询向量数据库并检索相关数据。然后,你可以通过相关数据查询 AI 模型,以获得响应。 检索增强生成(RAG)是在生成响应之前,通过使用训练数据之外的额外知识库来优化 LLM 输出的过程。 Embedding API Embedding API 可以将单词、句子、文档或图像转换为 Embedding 。Embedding 是单词、句子或文档的向量表示。

在 Spring Boot GraalVM 原生镜像中使用 Thymeleaf 布局和 Fragment 表达式

在 Spring Boot + Thyemleaf 的应用中,我们可以使用 thymeleaf-layout-dialect 来定义网页的通用布局,效果很好。 但是当我们将 Spring Boot 应用编译到 GraalVM 原生镜像时,却 出现了问题。 GraalVM Native Image: Generating 'demo' (executable)... ======================================================================================================================== [1/7] Initializing... (5,6s @ 0,32GB) Version info: 'GraalVM 22.3.1 Java 17 CE' Java version info: '17.0.6+10-jvmci-22.3-b13' C compiler: gcc (linux, x86_64, 11.3.0) Garbage collector: Serial GC 2 user-specific feature(s) - com.oracle.svm.polyglot.groovy.GroovyIndyInterfaceFeature - org.springframework.aot.nativex.feature.PreComputeFieldFeature Field org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build time Field org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build time Field org.

在 JPA 中使用 CriteriaQuery 执行 COUNT 查询

1、简介 Java Persistence API(JPA)是一种广泛使用的规范,用于访问、持久化和管理 Java 对象与关系数据库之间的数据。JPA 应用中的一项常见任务是计算符合特定条件的实体数量。使用 JPA 提供的 CriteriaQuery API 可以高效地完成这项任务。 CriteriaQuery 的核心组件是 CriteriaBuilder 和 CriteriaQuery 接口。CriteriaBuilder 是创建各种查询元素(如 Predicate、表达式和 CriteriaQuery)的工厂。而,CriteriaQuery 代表一个查询对象,它封装了 select、filter 和 order 标准。 本文将带你了解 JPA 中的 COUNT 查询,学习如何利用 CriteriaQuery API 轻松高效地执行 COUNT 操作。 本文以一个简单的图书管理系统为例,介绍如何利用 CriteriaQuery API 生成各种场景下的图书 COUNT 查询。 2、依赖 创建示例项目。 添加所需的 maven 依赖,包括 spring-data-jpa、spring-boot-starter-test 和 h2 内存数据库: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> 依赖添加完成后,创建图书管理系统示例。它允许我们执行各种查询,如统计所有图书,统计某个作者、书名和年份的图书的各种组合。 添加一个 Book (图书)实体,包含了 title、author、category 和 year 字段:

Hibernate 中的 load() 与 get()

1、简介 在 Hibernate 中,load() 和 get() 是用于从数据库检索数据的两种方法。本文将带你了解这两种方法之间的区别。 2、加载策略 Hibernate 中的 load() 方法采用了一种懒加载策略。调用该方法时,它会返回一个实体的代理对象,延迟数据库查询,直到访问对象的属性或方法时才执行查询。 如下: Person person = new Person("John Doe", 30); Session session = sessionFactory.getCurrentSession(); session.saveOrUpdate(person); Person entity = session.load(Person.class, person.getId()); assertNotNull(entity); assertEquals(person.getName(), entity.getName()); assertEquals(person.getAge(), entity.getAge()); 首先,创建一个新的 Person 对象并将其保存到数据库中。然后,使用 load() 根据 id 检索 Person 实体。虽然实体看起来是一个 Person 对象,但它只是 Hibernate 提供的一个代理对象。 当访问代理对象的属性(如 name 和 age)时,Hibernate 会拦截调用,并在必要时从数据库动态加载实际数据。相反,get()方法采用了急切加载策略,会立即查询数据库并返回实际实体对象: Person entity = session.get(Person.class, person.getId()); assertNotNull(entity); assertEquals(person.getName(), entity.getName()); assertEquals(person.getAge(), entity.getAge()); 3、数据存在时 当调用 load() 方法时,Hibernate 会用提供的主键 id 创建一个实体的代理对象。这个代理对象是实体数据的占位符,只填充了 id。实体的其余属性未初始化,将在首次访问时从数据库加载。如果试图在未初始化代理对象的情况下访问它的任何属性,就会抛出 LazyInitializationException 异常:

使用 Spring Authorization Server 和 PKCE 对 SPA 应用进行身份认证

1、简介 本文将带你了解如何在 OAuth 2.0 公开客户端(Public Client)中使用 Proof Key for Code Exchange (代码交换证明密钥,PKCE)。 2、背景 OAuth 2.0 公开客户端(如 SPA 单页应用,或使用授权码授权的移动应用)很容易受到授权码拦截攻击。如果客户端与服务器之间的通信是通过不安全的网络进行的,恶意攻击者就可能从授权端点截获授权代码。 如果攻击者可以访问授权码,就可以利用它获 Access Token。一旦攻击者拥有了 Access Token,就可以像合法应用用户一样访问受保护的应用资源,从而严重损害应用。例如,如果 Access Token 与金融应用相关联,攻击者就可能获取敏感的应用信息。 2.1、OAuth 授权码拦截攻击 来看看 Oauth 授权码拦截攻击是如何发生的: 上图展示了恶意攻击者如何滥用授权码获取 Access Token 的流程: 合法的 OAuth 应用使用其 Web 浏览器启动 OAuth 授权请求流程,并提供所有必要的详细信息。 Web 浏览器向授权服务器发送请求。 授权服务器向 Web 浏览器返回授权码。 在此阶段,如果通信是通过不安全的通道进行的,恶意用户可能会获取授权码。 恶意用户使用授权码从授权服务器获取 Access Token。 由于授权许可有效,授权服务器会向恶意应用签发 Access Token。恶意应用可以滥用 Access Token,代表合法应用访问受保护的资源。 代码交换证明密钥(Proof Key for Code Exchange,PKCE)是 OAuth 框架的一个扩展,旨在减轻这种攻击。 3、PKCE 和 OAuth PKCE(Proof Key for Code Exchange)扩展在 OAuth 授权码授权流程中包括以下额外步骤:

在 Spring Data JPA 查询中使用枚举(Enum)

1、概览 在使用 Spring Data JPA 构建持久层时,经常要处理带有枚举字段的实体。这些枚举字段代表一组固定的常量,例如订单的状态、用户的角色或业务的某个阶段。 本文将带你了解如何使用标准的 JPA 方法和原生查询来查询实体类中声明的枚举字段。 2、应用设置 2.1、数据模型 首先,定义数据模型,包括一个枚举字段。 我们示例中的中心实体是 Article 类,它声明了一个枚举字段 ArticleStage,用于表示文章可能处于的不同阶段: public enum ArticleStage { TODO, IN_PROGRESS, PUBLISHED; } ArticleStage 枚举包含三个可能的阶段,代表文章从最初创建到最终发布的生命周期。 接下来,创建声明了 ArticleStage 枚举字段的 Article 实体类: @Entity @Table(name = "articles") public class Article { @Id private UUID id; private String title; private String author; @Enumerated(EnumType.STRING) private ArticleStage stage; // 构造函数/Getter/Setter 方法省略 } 我们将 Article 实体类映射到 articles 数据库表。此外,还使用 @Enumerated 注解指定 stage 字段应作为字符串在数据库中持久化。 2.2、Repository 层 定义好数据模型后,就可以创建一个继承了 JpaRepository 的 Repository 接口,以便与数据库交互:

再谈谈 Spring 中的循环依赖

一、循环依赖 1.1、什么是循环依赖 首先,什么是循环依赖?这个其实好理解,就是两个 Bean 互相依赖,类似下面这样: @Service public class AService { @Autowired BService bService; } @Service public class BService { @Autowired AService aService; } AService 和 BService 互相依赖: 这个应该很好理解。 1.2、循环依赖的类型 一般来说,循环依赖有三种不同的形态,上面 1.1 小节是其中一种。 另外两种分别是三者依赖,如下图: 这种循环依赖一般隐藏比较深,不易发觉。 还有自我依赖,如下图: 一般来说,如果我们的代码中出现了循环依赖,则说明我们的代码在设计的过程中可能存在问题,我们应该尽量避免循环依赖的发生。不过一旦发生了循环依赖,Spring 默认也帮我们处理好了,当然这并不能说明循环依赖这种代码就没问题。实际上在目前最新版的 Spring 中,循环依赖是要额外开启的,如果不额外配置,发生了循环依赖就直接报错了。 另外,Spring 并不能处理所有的循环依赖,后面会和大家进行分析。 二、循环依赖解决思路 2.1、解决思路 那么对于循环依赖该如何解决呢?其实很简单,加入一个缓存就可以了。 来看下面这张图: 如上图所示,引入了一个缓存池。 当我们需要创建 AService 的实例的时候,会首先通过 Java 反射创建出来一个原始的 AService,这个原始 AService 可以简单理解为刚刚 new 出来(实际是刚刚通过反射创建出来)还没设置任何属性的 AService,此时,我们把这个 AService 先存入到一个缓存池中。 接下来我们就需要给 AService 的属性设置值了,同时还要处理 AService 的依赖,这时我们发现 AService 依赖 BService,那么就去创建 BService 对象,结果创建 BService 的时候,发现 BService 依赖 AService,那么此时就先从缓存池中取出来 AService 先用着,然后继续 BService 创建的后续流程,直到 BService 创建完成后,将之赋值给 AService,此时 AService 和 BService 就都创建完成了。

获取 Java JAR 文件中资源的路径

1、简介 在 Java 中,通常使用相对于 JAR 文件根目录的路径来访问JAR文件中的资源。 本文将带你了解获取 Java JAR 文件中资源路径的不同方法。 2、使用 Class.getResource() 方法获取资源的 URL Class.getResource() 方法提供了一种获取 JAR 文件中资源 URL 的直接方法。 该方法使用如下: @Test public void givenFile_whenClassUsed_thenGetResourcePath() { URL resourceUrl = GetPathToResourceUnitTest.class.getResource("/myFile.txt"); assertNotNull(resourceUrl); } 如上,调用 GetPathToResourceUnitTest.class 上的 getResource() 方法,并将资源文件路径 "/myFile.txt" 作为参数传递进去。然后,断言获取的 resourceUrl 不为 null,表示成功定位到资源文件。 3、使用 ClassLoader.getResource() 方法 还可以使用 ClassLoader.getResource() 方法访问 JAR 文件中的资源。在编译时不知道资源路径的情况下,该方法非常有用: @Test public void givenFile_whenClassLoaderUsed_thenGetResourcePath() { URL resourceUrl = GetPathToResourceUnitTest.class.getClassLoader().getResource("myFile.txt"); assertNotNull(resourceUrl); } 如上,使用类加载器 GetPathToResourceUnitTest.class.getClassLoader() 来获取资源文件。与前一种方法不同,该方法不依赖于类的包结构,它会在类路径的根级别搜索资源文件。 这意味着,无论资源位于项目结构中的哪个位置,它都能找到资源,从而更灵活地访问位于类包之外的资源。 4、Class.getResource() 和 ClassLoader.getResource() 的区别 Java 中的 Class.