使用 JdbcTemplate 调用存储过程

1、概览 本文将带你了解如何使用 Spring JDBC 框架的 JdbcTemplate 来调用存储过程。数据库存储过程类似于函数。函数支持输入参数并有返回类型,而存储过程同时支持输入和输出参数。 2、先决条件 来看看 PostgreSQL 数据库中一个简单的存储过程: CREATE OR REPLACE PROCEDURE sum_two_numbers( IN num1 INTEGER, IN num2 INTEGER, OUT result INTEGER ) LANGUAGE plpgsql AS ' BEGIN sum_result := num1 + num2; END; '; 存储过程 sum_twoo_numbers 接收两个输入数字,并在输出参数 sum_result 中返回它们的和。一般来说,存储过程可以支持多个输入和输出参数。但在本例中,我们只考虑了一个输出参数。 3、使用 JdbcTemplate#call() 方法 来看看如何使用 JdbcTemplate#call() 方法调用数据库存储过程: void givenStoredProc_whenCallableStatement_thenExecProcUsingJdbcTemplateCallMethod() { List<SqlParameter> procedureParams = List.of(new SqlParameter("num1", Types.INTEGER), new SqlParameter("num2", Types.NUMERIC), new SqlOutParameter("result", Types.NUMERIC) ); Map<String, Object> resultMap = jdbcTemplate.call(new CallableStatementCreator() { @Override public CallableStatement createCallableStatement(Connection con) throws SQLException { CallableStatement callableStatement = con.

JPA 中的 CAST 和 TREAT

1、简介 在 JPA 中,CAST 和 TREAT 是两个不同的关键字,用于操作数据类型和实体关系。本文将带你了解 CAST 和 TREAT 的区别,并通过示例来说明它们的用法。 2、JPA 中的 CAST JPA 中的 CAST 操作符主要用于 JPQL 查询中的类型转换。它允许我们显式地将一个值从一种数据类型转换为另一种数据类型。例如,可以使用 CAST 将字符串转换为整数,反之亦然。 CAST 的语法如下: CAST(expression AS type) expression 是我们要转换的值或字段,type 是我们要将表达式转换为的目标数据类型。 3、JPA 中的 TREAT 相比之下,TREAT 操作符是为在 JPQL 查询中对实体进行类型安全的向下转换而设计的。它在处理继承层次结构时特别有用。使用 TREAT 时,我们指定实体的子类型,然后 JPA 会检查实际实体是否确实属于该类型。 与 CAST 不同,TREAT 不会改变值的底层数据类型。相反,它允许我们像访问目标类型的值一样访问该值。 TREAT 的语法如下: TREAT(expression AS type) expression 是要处理的值,type 是目标数据类型。 4、适用场景和用法 在 JPA 查询中,CAST 和 TREAT 都用于处理类型转换,但它们的用途不同。 4.1、CAST 操作符 CAST 用于将一种数据类型转换为另一种数据类型,以便进行操作或比较。在执行查询时,如果需要的数据类型与数据库中存储的数据类型不同,通常会使用 CAST。 示例如下:一个名为 Employee 的实体,其 salary 字段在数据库中存储为字符串。下面是 Employee 实体的定义:

理解 Spring Reactive 中的 switchIfEmpty()

1、概览 本文将带你了解 Spring Reactive 中的 switchIfEmpty() 操作符及其在使用和不使用 defer() 操作符时的行为,了解这些操作符在不同场景中的交互方式,并通过实际示例来说明它们对响应式流(Reactive Stream)的影响。 2、switchIfEmpty() 和 Defer() 的使用 switchIfEmpty() 是 Mono 和 Flux 中的一个操作符,用于在源生产者为空时执行备用生产者流。如果主源 Publisher 没有发布数据,该操作符就会切换到替代源的数据发布。 考虑一个从大型文件中通过 ID 检索用户详细信息的场景。当请求文件中的用户详细信息时,遍历文件会消耗大量时间。因此,对于经常访问的 ID,将其详细信息缓存起来更有意义。 当端点收到请求时,首先搜索缓存。如果用户详细信息可用,返回响应。如果没有,则从文件中获取数据并缓存起来,以备后续请求。 在这种情况下,主数据提供者(Primary Data Provider)是检查缓存中 KEY 是否存在的流,而备用数据提供者是检查文件中的 KEY 并更新缓存的流。switchIfEmpty() 操作符可以根据缓存中数据的可用性高效地切换数据源提供者。 了解 defer() 操作符的使用也很重要,它可以推迟函数的求值,直到发生订阅。如果我们不在 switchIfEmpty() 中使用 defer() 操作符,表达式就会立即(急切地)求值,从而可能导致意想不到的副作用。 3、示例设置 通过示例来了解 switchIfEmpty() 操作符在不同情况下的行为。 3.1、Data Model 首先,定义一个 User 模型类,其中包含一些详细信息,如 id、email、username 和 roles: public class User { @JsonProperty("id") private String id; @JsonProperty("email") private String email; @JsonProperty("username") private String username; @JsonProperty("roles") private String roles; // Getter / Setter 省略 } 3.

使用 WebClient 执行同步请求

1、简介 本文将会带你了解如何使用 WebClient 执行同步请求。 在响应式编程日益普及的同时,在哪些情况下这种阻塞式请求仍然是适当和必要的? 2、Spring 中的 HTTP 客户端库概述 首先,回顾一下目前可用的客户端库。 在 Spring Framework 3.0 中引入 RestTemplate 时,其简单的 HTTP 请求模板方法 API 广受欢迎。然而,其同步性质和许多重载方法在高流量应用程序中导致了复杂性和性能瓶颈。 在 Spring 5.0 中,WebClient 被引入作为一种更高效、响应式的选择,用于非阻塞请求。尽管它是 Reactive Stack Web 框架的一部分,但它支持用于同步和异步通信的 Fluent 风格的 API。 在 Spring Framework 6.1 中,RestClient 提供了另一种执行 REST 调用的选项。它将 WebClient 的 Fluent API 与 RestTemplate 的基础设施结合在一起,包括消息转换器、请求工厂和拦截器。 尽管 RestClient 针对同步请求进行了优化,但如果我们的应用还需要异步或流式传输功能,则 WebClient 更为适用。通过在阻塞和非阻塞 API 调用中使用 WebClient,我们可以保持代码库的一致性,避免混用不同的客户端库。 3、阻塞与非阻塞 API 调用 在介绍各种 HTTP 客户端时,我们使用了同步和异步、阻塞和非阻塞等术语。这些术语与上下文有关,有时可能代表同一概念的不同名称。 在方法调用方面,WebClient 根据发送和接收 HTTP 请求和响应的方式,支持同步和异步交互。如果 WebClient 等待前一个请求完成后才继续处理后一个请求,则是以阻塞方式进行的,而结果是同步返回的。 另一方面,我们可以通过执行立即返回的非阻塞调用来实现异步交互。在等待另一个系统的响应时,其他处理工作可以继续进行,一旦准备就绪,就会以异步方式提供结果。 4、什么情况下使用同步请求 如前所述,WebClient 是 Spring Webflux 框架的一部分,默认情况下一切都是响应式的。不过,该库提供异步和同步操作支持,因此适用于响应式和 Servlet Stack Web 应用。

Gradle 排除传递依赖

1、概览 Gradle 是一款构建自动化工具,用于管理和自动化应用程序的构建、测试和部署过程。 使用基于 Groovy 或 Kotlin 的 DSL(Domain-Specific Language)来定义构建任务,可以轻松地自动定义和管理项目中所需的依赖。 本文将带你了解在 Gradle 中排除传递依赖的几种方法。 2、传递依赖是什么? 假设我们使用的 A 依赖于另一个库 B。默认情况下,当我们包含 A 时,Gradle 会自动将 B 添加到项目的 classpath 中,这样,即使我们没有明确地将 B 添加为依赖,也可以在项目中使用 B 的代码。 来看一个实际的例子,在项目中定义 Google Guava 依赖: dependencies { // ... implementation 'com.google.guava:guava:31.1-jre' } 如果 Google Guava 与其他库存在依赖关系,那么 Gradle 会自动包含这些其他库。 要查看项目中使用的依赖项,可以使用如下命令将其打印出来: ./gradlew <module-name>:dependencies 假设我们的模块名为 excluding-transitive-dependencies: ./gradlew excluding-transitive-dependencies:dependencies 输出如下: testRuntimeClasspath - Runtime classpath of source set 'test'. \--- com.google.guava:guava:31.1-jre +--- com.google.guava:failureaccess:1.0.1 +--- com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava +--- com.google.code.findbugs:jsr305:3.0.2 +--- org.

Spring Data JPA 中的 @DynamicInsert

1、概览 Spring Data JPA 中的 @DynamicInsert 注解通过在 SQL 语句中只包含非 null 字段来优化插入操作。这一过程加快了结果查询的速度,减少了不必要的数据库交互。 虽然它提高了对具有许多可为空字段的实体的效率,但也引入了一些运行时开销。因此,在只有在排除空列的好处超过性能成本的情况下,有选择地使用它。 2、JPA 中 INSERT 的默认行为 使用 EntityManager 或 Spring Data JPA 的 save() 方法持久化 JPA 实体时,Hibernate 会生成一条 SQL 插入(INSERT)语句。该语句包括每个实体列,即使某些列包含 null 值。因此,在处理包含许多可选字段的大型实体时,插入操作的效率会很低。 先来看一个简单的 Account 实体: @Entity public class Account { @Id private int id; @Column private String name; @Column private String type; @Column private boolean active; @Column private String description; // Getter / Setter } 为 Account 实体创建 JPA Repository:

在 Docker 中构建多模块 Maven 项目

1、概览 本文将带你了解如何利用 Docker 的多阶段构建功能高效地为多模块 Maven 项目构建 Docker 镜像,,以充分利用 Docker 的缓存机制。 然后,还会介绍 Google Jib Maven 插件,用于在没有 Dockerfile 或 Docker 的情况下构建 Docker 镜像。 2、多模块 Maven 项目 多模块 Maven 应用由不同功能的独立模块组成。Maven 通过管理依赖关系来构建应用,并将这些模块组装成一个可部署的单元。 在本文的代码示例中,我们将使用一个包含两个 Maven 模块的基本 Spring Boot 项目,这两个模块分别代表应用程序的 Domain 和 API。 Maven 项目的结构如下: +-- parent +-- api | `-- src | `-- pom.xml +-- domain | `-- src | `-- pom.xml `-- pom.xml 查看父模块的 pom.xml 文件,就会发现它继承了 spring-boot-starter-parent,并包含了 domain 和 api 模块: <project> <groupId>com.baeldung.docker-multi-module-maven</groupId> <artifactId>parent</artifactId> <packaging>pom</packaging> <version>0.

Java 反射中的 AccessFlag(访问标志)

1、概览 Java 中的反射是一个强大的功能,它允许我们操纵不同的成员,如类、接口、字段和方法。此外,使用反射,我们可以在编译时实例化类、调用方法和访问字段,而无需知道类型。 本文将带你了解如何使用 JVM AccessFlag(访问标志),以及 Modifier 和 AccessFlag 之间的区别。 2、JVM AccessFlag Java 虚拟机规范 定义了 JVM 中已编译类的结构,它由一个 ClassFile 组成: ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; } 除其他项目外,ClassFile 还包含 access_flags 项。简而言之,access_flags 是一个掩码,由定义类的访问权限和其他属性的各种标志组成。 此外,ClassFile 还包括 field_info 和 method_info 项,每个项都包含其 access_flags 项。 Javassist 和 ASM 等库使用 JVM AccessFlag 来操作 Java 字节码。

在运行时更改 Spring Boot 属性的几种方式

1、概览 动态管理应用程序配置是许多实际应用场景中的关键要求。在微服务架构中,由于扩展操作或负载条件的变化,不同的服务可能需要即时更改配置。在其他情况下,应用程序可能需要根据用户偏好、来自外部 API 的数据调整其行为,或满足动态变化的要求。 application.properties 文件是静态的,在不重启应用的情况下无法更改。不过,Spring Boot 提供了几种强大的方法,可在运行时调整配置而无需停机。无论是在实时应用程序中切换功能、更新数据库连接以实现负载均衡,还是在不重新部署应用的情况下更改第三方集成的 API Key,Spring Boot 的动态配置功能都能为这些复杂的环境提供所需的灵活性。 本文将带你了解几种无需直接修改 application.properties 文件即可动态更新 Spring Boot 应用程序中属性的策略。这些方法可满足不同的需求,从非持久性内存更新到使用外部文件进行持久性更改。 本文中的示例使用 Spring Boot 3.2.4、JDK17 以及 Spring Cloud 4.1.3。不同版本的 Spring Boot 可能需要对代码进行轻微调整。 2、使用 Prototype Scope Bean 当我们需要动态调整特定 Bean 的属性,而不影响已创建的 Bean 实例或更改全局应用程序状态时,一个简单的使用 @Value 直接注入的 @Service 类是不够的,因为这些属性在 Application Context 的生命周期内是静态的。 相反,可以使用 @Configuration 类中的 @Bean 方法创建具有可修改属性的 Bean。这种方法允许在应用程序执行过程中动态更改属性: @Configuration public class CustomConfig { @Bean @Scope("prototype") // Scope 为 prototype public MyService myService(@Value("${custom.property:default}") String property) { return new MyService(property); } } 通过使用 @Scope("prototype") 注解,我们可以确保每次调用 myService(.

在 Java Servlet 中读取、解析 POST 请求数据

1、简介 Java Servlet 是一个服务端组件,用于处理客户端传入的 HTTP 请求,通常我们需要通过 Servlet 中的 HttpServletRequest 对象来获取到客户端提交的请求数据。 本文将带你了解在 Java Servlet 中读取 Payload(即请求体)数据的各种方法,以及最佳实践和注意事项。 2、理解 Request Payload Post 请求主要用于通过 HTTP 请求向服务器发送数据。这些数据可以是任何内容,从包含用户输入的表单数据到结构化数据如 JSON 和 XML,甚至是二进制文件。这些数据位于请求体中,与 URL 分开。这样可以实现更广泛和安全的数据传输。我们可以通过请求中的 Content-Type Header 标识不同类型的数据。 常见的 Content-Type 包括: application/x-www-form-urlencoded:用于以键值对形式编码的表单数据 application/json:用于 JSON 格式的数据 application/xml:用于 XML 格式的数据 text/plain:用于发送纯文本 multipart/form-data:用于上传二进制文件和常规表单数据(form data) 3、读取 Post 请求体的方式 接下来,让我们看看从 POST Payload 中提取数据的不同方法。 3.1、使用 getParameter() 获取 URL 编码的表单数据 我们可以使用 HttpServletRequest 接口提供的 getParameter() 方法,使用通过 POST 请求提交的参数名检索特定表单数据。该方法使用表单参数名作为参数,并以字符串(String)形式返回相应的值。 举个例子: @WebServlet(name = "FormDataServlet", urlPatterns = "/form-data") public class FormDataServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String firstName = StringEscapeUtils.