教程

防止 Gson 将整数(Integer)表示为浮点数(Float)

1、简介 在将 Java 对象序列化和反序列化为 JSON 格式或从 JSON 格式反序列化 Java 对象时,Google 开发的 Gson 库是一个不错的选择。但是,在序列化对象时,我们通常会遇到 Gson 将整数显示为浮点数的问题。 本文将带你了解为什么在 Gson 的设计中整数被视为浮点数?以及如何解决这个问题。 2、问题的定义 Gson 可将 Java 对象序列化为 JSON。默认情况下,Gson 会将整数序列化为浮点数,以获得更精确的表示。下面是一个简单的例子: public String jsonString= "[{\"id\":4077395,\"field_id\":242566,\"body\":\"\"}, " + "{\"id\":4077398,\"field_id\":242569,\"body\":[[273019,0],[273020,1],[273021,0]]}, " + "{\"id\":4077399,\"field_id\":242570,\"body\":[[273022,0],[273023,1],[273024,0]]}]"; 如上,我们声明了一个名为 jsonString 的 JSON 字符串,它代表一个对象数组。这个 JSON 数组有不同的字段,如 id、field_id 和 body。 现在,我们使用 Gson 库将 JSON 字符串反序列化为 Hashtable<String, Object> 对象列表。 ArrayList<Hashtable<String, Object>> responses; Type ResponseList = new TypeToken<ArrayList<Hashtable<String, Object>>>() {}.getType(); responses = new Gson().fromJson(jsonString, ResponseList); 如上,我们声明了一个名为 responses 的 ArrayList,其中包含 Key 为字符串、Value 为对象的 Hashtable 类型元素。此外,我们还利用 Gson 库将 jsonString 反序列化为 Hashtables 列表。

在 Spring Batch 中访问 JobParameter

1、概览 Spring Batch 是一个强大的 Java 批处理框架,因此在数据处理活动和定时任务运行中被广泛选择。根据业务逻辑的复杂程度,作业可以依赖不同的配置值和动态参数。 本文将带你了解如何使用 JobParameter 以及如何从基本的批处理组件中访问它们。 2、Demo 项目 我们将为药房服务开发一个 Spring Batch。主要业务任务是查找即将过期的药品,根据销售情况计算新价格,并通知消费者即将过期的药品。此外,我们将从内存中的 H2 数据库读取数据,并将所有处理细节写入日志,以简化实现过程。 2.1、依赖 要开始演示应用,需要添加 Spring Batch 和 H2 依赖: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-batch</artifactId> <version>3.2.0</version> </dependency> 可以在 Maven Central Repository 中找到最新的 H2 和 Spring Batch 版本。 2.2、准备测试数据 首先在 schema-all.sql 中定义 Schema: DROP TABLE medicine IF EXISTS; CREATE TABLE medicine ( med_id VARCHAR(36) PRIMARY KEY, name VARCHAR(30), type VARCHAR(30), expiration_date TIMESTAMP, original_price DECIMAL, sale_price DECIMAL ); 初始测试数据定义在 data.

Hibernate 和 Spring Data JPA 中的 N+1 问题

1、概览 Spring JPA 和 Hibernate 为无缝数据库通信提供了强大的工具。不过,由于客户端将更多控制权委托给了框架,因此生成的查询可能远非最佳。 本文将带你了解使用 Spring JPA 和 Hibernate 时常见的 N+1 问题,以及可能导致该问题的不同情况。 2、社交媒体平台 为了更好地将问题形象化,我们需要概述实体之间的关系。以一个简单的社交网络平台为例。这里只有用户(User)和帖子(Post): 我们在图表中使用了 Iterable,并且我们将为每个示例提供具体的实现:List 或 Set。 为了测试请求的数量,我们将使用一个专用库,而不是检查日志。不过,我们会参考日志,以便更好地了解请求的结构。 如果在每个示例中没有明确指定关系的获取类型(Fetch Type),则默认情况下假定为默认值。所有的一对一关系都使用急切加载(Eager Fetch),而一对多关系则使用延迟加载(Lazy)。此外,代码示例中使用了 Lombok 来减少代码中的冗余。 3、N+1 问题 N+1 问题指的是,对于单个请求(例如检索用户),会对每个用户发出额外请求,以获取其信息。虽然这个问题通常与懒加载有关,但并非总是如此。 任何类型的关系都可能出现这种问题。不过,它通常出现在多对多或一对多关系中。 3.1、延迟加载 首先,来看看懒加载是如何导致 N+1 问题的,示例如下: @Entity public class User { @Id private Long id; private String username; private String email; @OneToMany(cascade = CascadeType.ALL, mappedBy = "author") protected List<Post> posts; // 构造函数/getter/setter } User 与 Post 之间是一对多的关系。这意味着每个 User 都有多个 Post。我们没有明确确定字段的 Fetch 策略。策略是从注解中推断出来的。如前所述,@OneToMany 默认采用 Lazy Fetch 策略:

在 Spring 中使用 AOP 记录执行日志

1、概览 Aspect-Oriented Programming(面向切面编程,简称 AOP)是一种范式,它能让我们在整个应用中隔离事务管理或日志记录等交叉问题,而不会干扰业务逻辑。 本文将带你了解如何在 Spring 中使用 AOP 记录执行日志。 2、不使用 AOP 记录日志 通常,我们会在方法的开始和结束处输出日志。这样,就能跟踪应用的执行流程。此外,还可以捕获传递给特定方法的值及其返回值。 示例如下: public String greet(String name) { logger.debug(">> greet() - {}", name); String result = String.format("Hello %s", name); logger.debug("<< greet() - {}", result); return result; } 尽管上面的实现看起来是一个标准的解决方案,但日志语句在代码中增加了复杂性。 如果没有日志记录,只需要一行代码就可以完成: public String greet(String name) { return String.format("Hello %s", name); } 3、面向切面(AOP)编程 顾名思义,面向切面的编程(Aspect-Oriented Programming)侧重于切面,而不是对象和类。我们可以使用 AOP 编程为特定应用实现额外功能,而无需修改其当前实现。 3.1、AOP 概念 在深入学习之前,让我们先从高层次来了解一下 AOP 的基本概念。 切面(Aspect):横切关注点或我们希望在整个应用中应用的功能。 连接点(Join Point):应用流程中我们希望应用切面的点。 Advice:在特定连接点应执行的操作。 Pointcut:应在其中应用某一切面的连接点集合。 另外,Spring AOP 仅支持方法执行的连接点。可以考虑使用 AspectJ 等编译时库为字段、构造函数、静态初始化器等创建切面。

JPA @OneToMany 关系中的 List 与 Set

1、概览 Spring JPA 和 Hibernate 为不同据库通信提供了强大的工具。然而,随着开发者将更多的控制权(包括查询生成)委托给框架,结果可能与我们的预期相去甚远。 开发者通常会对在多对多关系中使用列表(List)还是集合(Set)产生困惑。而且,Hibernate 对其 bag、list 和 set 使用了类似的名称,但它们之间有稍微不同的含义,这进一步增加了混淆的可能性。 在大多数情况下,Set 更适用于一对多或多对多关系。不过,它们对性能有特殊的影响,需要注意。 本文将带你了解 JPA 实体关系中 List 和 Set 的区别,以及各自的优缺点。 2、测试 这里使用专门的测试库来测试请求数。检查日志不是一个好的解决方案,因为它不是自动化的,可能只适用于简单的示例。当请求产生数十或数百个查询时,使用日志是不够高效的。 首先,需要 io.hypersistence。注意,artifact ID 中的数字是 Hibernate 版本: <dependency> <groupId>io.hypersistence</groupId> <artifactId>hypersistence-utils-hibernate-63</artifactId> <version>3.7.0</version> </dependency> 此外,还使用 util 库进行日志分析: <dependency> <groupId>com.vladmihalcea</groupId> <artifactId>db-util</artifactId> <version>1.0.7</version> </dependency> 我们应该使用所提供的 Util 来 封装数据源,使其正常工作。这可以通过 BeanPostProcessor 来做实现: @Component public class DataSourceWrapper implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof DataSource originalDataSource) { ChainListener listener = new ChainListener(); SLF4JQueryLoggingListener loggingListener = new SLF4JQueryLoggingListener(); loggingListener.

使用 Spring Data JPA 在 PostgreSQL 中存储和检索 JSON 数据,

1、概览 本文将带你全面了解如何使用 Spring Data JPA 在 PostgreSQL JSONB 列中存储、检索 JSON 数据。 2、VARCHAR 映射 本节将介绍如何使用 AttributeConverter 将 VARCHAR 类型的 JSON 值转换为自定义 Java POJO。 其目的是方便 Java 数据类型中的实体属性值与数据库列中的相应值之间的转换。 2.1、Maven 依赖 要创建 AttributeConverter,必须在 pom.xml 中加入 Spring Data JPA 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.7.18</version> </dependency> 2.2、数据表定义 数据库表定义如下: CREATE TABLE student ( student_id VARCHAR(8) PRIMARY KEY, admit_year VARCHAR(4), address VARCHAR(500) ); student 表有三个字段,其中我们希望 address 列能存储具有以下结构的 JSON 值: { "postCode": "TW9 2SF", "city": "London" } 2.3、Entity 类 创建一个相应的 POJO 类,用 Java 表示 address 数据:

Spring Webclient 自定义 JSON 反序列化

1、概览 本文将带你了解如何在 Spring WebClient 中自定义 JSON 的反序列化(Deserialization)。 2、为什么需要自定义反序列化? Spring WebFlux 模块中的 Spring WebClient 通过 Encoder 和 Decoder 组件处理序列化和反序列化。编码器(Encoder)和解码器(Decoder)是表示读取和写入内容的接口。默认情况下,spring-core 模块提供 byte[]、ByteBuffer、DataBuffer、Resource 和 String 编码器和解码器实现。 Jackson 是一个 JSON 库,它通过 ObjectMapper 将 Java 对象序列化为 JSON,并将 JSON 字符串反序列化为 Java 对象。ObjectMapper 包含内置的配置选项,可以使用 Deserialization Feature 进行开启或关闭。 当 Jackson 库提供的默认行为无法满足我们的特定需求时,就有必要定制反序列化过程。为了在序列化和反序列化过程中修改行为,ObjectMapper 提供了一系列配置选项供我们设置。因此,我们需要将这个自定义的 ObjectMapper 注册到 Spring WebClient 中,以便在序列化和反序列化中使用。 3、如何自定义 ObjectMapper? 自定义 ObjectMapper 可以在全局应用程序级别与 WebClient 关联,也可以与特定请求关联。 以一个获取客户订单详细信息的 GET 端点为例。 OrderResponse Model 如下: { "orderId": "a1b2c3d4-e5f6-4a5b-8c9d-0123456789ab", "address": [ "123 Main St", "Apt 456", "Cityville" ], "orderNotes": [ "Special request: Handle with care", "Gift wrapping required" ], "orderDateTime": "2024-01-20T12:34:56" } 对于上述响应的一些反序列化规则如下:

Spring Boot 实现 gPRC 服务调用

1、简介 gRPC 是一个高性能的开源 RPC 框架,最初由谷歌开发。它有助于消除样板代码,并在数据中心内外连接多语言服务。该 API 基于 Protocol Buffer,它提供了一个 protoc 编译器,用于生成不同支持的语言的代码。 我们可以将 gRPC 视为 REST、SOAP 或 GraphQL 的替代品,它建立在 HTTP/2 的基础上,可以使用多路复用或流式连接等功能。 本文将带你了解如何使用 Spring Boot 实现 gRPC 服务提供者和消费者。 2、面临的问题 首先,Spring Boot 并不直接支持 gRPC。它只支持 Protocol Buffer,允许我们实现基于 protobuf 的 REST 服务。因此,需要通过使用第三方库来加入 gRPC: 平台相关的编译器:protoc 编译器是平台相关的。因此,如果在构建过程中需要生成存根(Stub),构建过程会变得更加复杂且容易出错。 依赖: 需要在 Spring Boot 应用中添加兼容的依赖。不幸的是,Java 的 protoc 添加了 javax.annotation.Generated 注解(Issues),这迫使我们需要添加一个依赖项来编译旧的 Java EE 注解库。 服务器运行时:gRPC 服务提供者需要在服务器中运行。gRPC for Java 项目提供了一个 shaded Netty,我们需要将其包含在 Spring Boot 应用中,或者用 Spring Boot 已经提供的服务器来代替。 消息传输: Spring Boot 提供了不同的客户端,如 RestClient(阻塞)或 WebClient(非阻塞),但遗憾的是,这些客户端无法配置和用于 gRPC,因为 gRPC 在阻塞和非阻塞调用中都使用了 自定义 transport 技术。 配置: 由于 gRPC 使用了自己的技术,我们需要配置属性以按照 Spring Boot 的方式对其进行配置。 3、示例项目 幸运的是,可以使用第三方 Spring Boot Starter 来应对这些挑战,例如 LogNet 或 grpc ecosystem project。这两个 Starter 都很容易整合,但后者同时支持提供者和消费者以及许多其他集成功能,因此本文选择了后者。

使用 Spring Modulith 实现事件外部化

1、概览 本文将带你了解如何使用 Spring Modulith 来监听 Spring Application Event 并自动将其发布到 Kafka Topic。 2、事务操作和 Message Broker 假设,正在开发一个保存文章的功能: @Service class Baeldung { private final ArticleRepository articleRepository; // 构造函数 @Transactional public void createArticle(Article article) { validateArticle(article); article = addArticleTags(article); // ...其他业务逻辑 articleRepository.save(article); } } 此外,还需要向系统的其他部分通知这一新文章。其他模块或服务会根据此做出反应,例如创建报告或向网站读者发送新闻邮件。 最简单的方法是使用 KafkaOperations 向 baeldung.articles.published Kafka Topic 发送消息,并使用文章的 slug() 作为关 Key: @Service class Baeldung { private final ArticleRepository articleRepository; private final KafkaOperations<String, ArticlePublishedEvent> messageProducer; // 构造函数 @Transactional public void createArticle(Article article) { // .

从 HttpServletRequest 获取查询参数

1、概览 后端 HTTP API 开发最重要的功能之一是解析前端传递的请求查询参数。 本文将带你了解几种直接从 HttpServletRequest 获取查询参数的方法,以及 Spring MVC 提供的一些简洁方法。 2、HttpServletRequest 中的方法 首先,来看看 HttpServletRequest 提供的与参数相关的方法。 2.1、HttpServletRequest#getQueryString() HttpServletRequest#getQueryString() 可以直接从 URL 获取查询字符串信息: @GetMapping("/api/byGetQueryString") public String byGetQueryString(HttpServletRequest request) { return request.getQueryString(); } 使用 curl 向该 API 发送一个包含多个参数的 GET 请求时,getQueryString() 方法会返回 ? 后面的所有字符: $ curl 'http://127.0.0.1:8080/spring-mvc-basics/api/byGetQueryString?username=bob&roles=admin&roles=stuff' username=bob&roles=admin&roles=stuff 如果将 @GetMapping 更改为 @RequestMapping,当使用 POST/PUT/PATCH/DELETE HTTP 方法发送请求时,返回的响应相同。也就是说 HttpServletRequest#getQueryString 始终获取到的是 URL 中的查询参数,无论 HTTP 方法是什么。因此,本教程只关注GET请求。 2.2、HttpServletRequest#getParameter(String) 为了简化参数的解析,HttpServletRequest 提供了一个 getParameter 方法,可以通过参数名获取参数值: @GetMapping("/api/byGetParameter") public String byGetParameter(HttpServletRequest request) { String username = request.