Java

调整 JDBC 连接池大小的最佳实践

1、简介 本文将带你了解调整 JDBC 连接池大小的最佳策略。 2、什么是 JDBC 连接池,为什么要使用它? JDBC 连接池是一种用于有效管理数据库连接的机制。创建数据库连接需要几个耗时的步骤,如 打开数据库连接 验证用户身份 创建用于通信的 TCP Sokcet 套接字 通过套接字收发数据 关闭连接和 TCP Sokcet 套接字 为每个用户请求重复这些步骤的效率可能会很低,尤其是对于有许多用户的应用。JDBC 连接池通过提前创建一个可重复使用的连接池来解决这个问题。当应用程序启动时,它会在池中创建并维护数据库连接。池连接管理器负责管理这些连接并处理其生命周期。 当客户端请求连接时,连接池管理器会从连接池中提供一个连接,而不是创建一个新的连接。一旦客户端完成操作,连接就会返回池中重复使用,而不是关闭。这种连接的重复使用节省了时间和资源,大大提高了应用程序的性能。 3、为什么 JDBC 连接池的大小对应用来说很重要? 确定 JDBC 连接池的最佳大小对于平衡性能和资源利用率至关重要。较小的连接池可能会加快连接访问速度,但如果没有足够的连接来满足所有请求,则可能导致延迟。相反,较大的连接池可确保有更多连接可用,减少在队列中花费的时间,但可能会降低连接表的访问速度。 “连接表” 通常指的是管理和跟踪数据库连接的内部数据结构。它记录了当前连接的状态(如空闲、使用中等)以及相关的元数据,以便有效地分配和管理连接。这个“表”并不是数据库中的实际表,而是连接池实现中的一个逻辑概念。 下表总结了在确定连接池大小时需要考虑的利弊: 连接池大小 优点 缺点 较小的连接池 更快地访问连接表 可能需要更多连接来满足请求。请求在队列中停留的时间可能会更长 较大的连接池 有更多连接来满足请求。请求在队列中花费的时间减少(或没有) 降低访问连接表的速度 4、确定 JDBC 连接池大小时应考虑的要点 在确定池大小时,需要考虑几个因素。首先,应该评估平均事务响应时间和花费在数据库查询上的时间。负载测试可以帮助确定这些时间,建议在计算连接池大小时再增加 25% 的容量,以应对意外负载。其次,连接池应能根据实际需要进行增减。我们可以使用日志或 JMX 监视来监控系统,从而动态调整池的大小。 此外,还应考虑每次页面加载执行多少次查询以及每次查询的持续时间。为了获得最佳性能,我们可以从少量连接开始,然后逐渐增加。每个节点 8 到 16 个连接的池通常是最佳的。我们还可以根据监控统计数据调整空闲超时(Idle Timeout)和池大小调整数量(Pool Resize Quantity)值。 5、JDBC 连接池的基本控制设置 这些基本设置可控制池的大小: 连接池属性 说明 初始和最少连接数 池创建时的大小及其允许的最小连接数量 池最大连接数 连接池可维护的最大连接数量 池大小调整数量 空闲超时时要移除的连接数。闲置时间超过超时的连接将被移除,一旦连接池达到初始和最小连接池大小,就会停止移除。 最大空闲连接数 池中允许的最大闲置连接数。如果闲置连接数超过此限制,多余的连接将被关闭,从而释放资源 最小空闲连接数 在连接池中保留的空闲连接的最小数量 最大等待时间 应用等待连接可用的最长时间 验证查询 用于验证连接的 SQL 查询,然后再将其交给应用 6、调整 JDBC 连接池大小的最佳实践 以下是调整 JDBC 连接池以确保与数据库实例之间健康连接的一些最佳实践。

Java 21 中对 Emoji 表情支持的改进

1、概览 Java 21 在 java.lang.Character 类中引入了一组新方法,为 Emoji 表情符号提供更好的支持。通过这些方法,我们可以轻松检查某个字符是否是 Emoji 表情符号,并检查 Emoji 表情符号的属性和特征。 本文将带你了解这些新添加的方法,以及与 Java 21 中 Emoji 表情符号处理相关的关键概念。 2、Character API 的更新 Java 21 在 java.lang.Character 类中引入了六个与 Emoji 表情符号处理相关的新方法。所有新方法都是静态的,以代表字符 Unicode 码点的 int 作为参数,并返回 boolean 值。 Unicode 码点是分配给 Unicode 标准中每个字符的唯一数值。它代表不同平台和语言中的特定字符。例如,码点 U+0041 代表字母 A,十六进制形式为 0x0041。 现在,让我们来仔细看看这些与 Emoji 表情符号相关的新方法。 2.1、isEmoji() isEmoji(int codePoint) 方法是新 emoji 方法中最基本的方法。它接收代表字符 Unicode 码点的 int 值,并返回一个 boolean 值,表示该字符是否为 Emoji。 用法如下; String messageWithEmoji = "Hello Java 21! 😄"; String messageWithoutEmoji = "Hello Java!

使用 @Valid 注解校验嵌套对象

1、简介 本文将带你了解如何使用 @Valid 注解来验证对象及其嵌套的子对象。 当传入数据是基本数据类型,比如 Integer 或 String 时,验证数据可以很简单。然而,当传入信息是一个对象,特别是一个对象图时,验证可能会更加困难。幸运的是,@Valid 注解简化了对嵌套子对象的验证。 2、@Valid 注解是啥 @Valid 注解来自 Jakarta Bean Validation 规范,用于标记需要验证的特定参数。 使用该注解可确保传递给方法或存储在字段中的数据符合指定的验证规则。这有助于提高数据的完整性和一致性。 当在 JavaBean 的字段或方法上使用时,它会触发所有已定义的约束检查。Bean Validation API 中最常用的一些约束包括 @NotNull、@NotBlank、@NotEmpty、@Size、@Email、@Pattern 等。 3、在子对象上使用 @Valid 注解 首先,必须确定验证规则,并对字段应用前面提到的验证约束。 接下来,定义一个 Project 类,该类包含一个嵌套的 User 对象,并用 @Valid 注解来装饰该对象: public class Project { @NotBlank(message = "Project title must be present") @Size(min = 3, max = 20, message = "Project title size not valid") private String title; @Valid // 校验嵌套的对象 private User owner; // 构造函数、Getter、Setter 方法省略 } public class User { // 校验规则 @NotBlank(message = "User name must be present") @Size(min = 3, max = 50, message = "User name size not valid") private String name; // 校验规则 @NotBlank(message = "User email must be present") @Email(message = "User email format is incorrect") private String email; // 构造函数、Getter、Setter 方法省略 } 之后,通过 Validator 实例的 validate() 方法来验证对象:

Java 和 Guava 中的线程池

1、概览 本文将带你了解 Java 中的线程池。首先介绍 Java 中的标准库,然后介绍 Google 的 Guava 库。 2、线程池 在 Java 中,线程被映射到系统级线程,而系统级线程是操作系统的资源。如果不加控制地创建线程,这些资源可能很快就会耗尽。 操作系统也会在线程之间进行上下文切换,以模拟并行处理。一个简单的观点是,创建的线程越多,每个线程实际工作的时间就越少。 线程池模式有助于在多线程应用中节省资源,并将并行性控制在某些预定义的范围内。 使用线程池时,我们将并发代码编写为并行任务的形式,并将它们提交给线程池实例执行。这个实例控制着多个可重复使用的线程来执行这些任务。 该模式允许我们控制应用创建的线程数量及其生命周期。还能调度任务的执行,并将接收到的任务保存在队列中。 3、Java 中的线程池 3.1、Executors、Executor 和 ExecutorService Executors 工具类包含多个用于创建预配置线程池实例的方法。这些类是一个很好的起点。如果不需要进行任何自定义微调,就可以使用它们。 在 Java 中,我们使用 Executor 和 ExecutorService 接口来处理不同的线程池实现。通常,应该使代码与线程池的实际实现解耦,并在整个应用中使用这些接口。 3.1.1、Executor Executor 接口有一个 execute 方法,用于提交实现了 Runnable 接口的实例以供执行。 来看一个快速示例,演示如何使用 Executors API 获取由单线程池和无界队列支持的 Executor 实例,以便按顺序执行任务。 如下,运行一个任务,只需在屏幕上打印 “Hello World” 即可。我们以 lambda(Java 8 的一个特性)的形式提交任务,它被推断为 Runnable: Executor executor = Executors.newSingleThreadExecutor(); executor.execute(() -> System.out.println("Hello World")); 3.1.2、ExecutorService ExecutorService 接口包含大量用于控制任务进度和管理服务终止的方法。使用该接口,我们可以提交任务以供执行,还可以使用返回的 Future 实例控制任务的执行。 现在,创建一个 ExecutorService,提交一项任务,然后使用返回的 Future 的 get 方法等待提交的任务完成并返回结果值:

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。

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 创建的新线程设置自定义线程名称:

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.

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.