教程

使用 Java 在 PostgreSQL 中存储日期和时间

1、简介 在数据库中存储日期(Date)和时间(Time)信息是软件开发中的一项常见任务。由于有许多不同的格式、时区和存储格式,处理日期和时间可能是一项复杂的任务,如果处理不慎,可能会导致许多问题。 本文将带你了解 Java Date / Time API 提供的日期和时间类,以及 PostgreSQL 如何持久化这些类。 2、设置 本文使用 Spring Boot 和 Spring Data JPA 在 PostgreSQL 数据库中持久化日期和时间值。 首先,创建一个实体,其中包含 Java Date / Time API 中不同日期和时间类的字段: @Entity public class DateTimeValues { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; private Date date; private LocalDate localDate; private LocalDateTime localDateTime; private Instant instant; private ZonedDateTime zonedDateTime; private LocalTime localTime; private OffsetDateTime offsetDateTime; private java.sql.Date sqlDate; // Getter / Setter 省略 } 此外,还要添加一个默认构造函数,以固定时间初始化所有日期/时间字段:

JsonMappingException:Can not deserialize instance of java.util.HashMap out of START_ARRAY token

1、概览 本文将带你了解如何解决 Jackson 异常:JsonMappingException: Can not deserialize instance of java.util.HashMap out of START_ARRAY token。 2、理解异常 简而言之,Jackson 在反序列化 JSON 字符串时抛出 JsonMappingException 来指示映射错误。而,“Can not deserialize instance of java.util.HashMap out of START_ARRAY token” 异常消息表示预期的数据结构与实际的 JSON 字符串不匹配。 这里出现不匹配是因为 Jackson 期望的是 List 而不是 HashMap。反序列化过程不知道如何将指定的 JSON 数组转换为 HashMap。因此出现异常。 3、示例 来看一个会导致 Jackson 出现 JsonMappingException 异常的例子。 为了简单起见,假设我们有一个 JSON 数组,其中每个元素都代表一个由名(firstName)和姓(lastName)定义的人: [ { "firstName":"Abderrahim", "lastName":"Azhrioun" }, { "firstName":"Nicole", "lastName":"Smith" } ] 现在,如果尝试将 JSON 数组直接反序列化为 Map,将导致 JsonMappingException: @Test public void givenJsonArray_whenDeserializingToMap_thenThrowException() { final String json = "[{\"firstName\":\"Abderrahim\",\"lastName\":\"Azhrioun\"}, {\"firstName\":\"Nicole\",\"lastName\":\"Smith\"}]"; final ObjectMapper mapper = new ObjectMapper(); Exception exception = assertThrows(JsonMappingException.

MongoDB 字段级加密

1、简介 本文将带你了解如何使用 MongoDB 的客户端字段级加密(或 CSFLE)来加密文档中的指定字段,主要介绍显式/自动加密和显式/自动解密,以及加密算法之间的差异。 2、场景与设置 MongoDB Atlas 和 MongoDB Enterprise 都支持自动加密。MongoDB Atlas 有一个 永久免费的集群,我们可以用它来测试所有功能。 此外,需要注意的是,字段级加密有别于静态存储,后者会对整个数据库或磁盘进行加密。通过有选择地加密特定字段,我们可以更好地保护敏感数据,同时实现高效查询和索引。 本文将从一个简单的 Spring Boot 应用开始,使用 Spring Data MongoDB 插入和检索数据。 首先,我们要创建一个包含未加密字段和加密字段混合的文档类。我们将从手动加密开始,然后看看如何通过 自动加密 实现同样的效果。对于手动加密,我们需要一个中间对象来表示加密的 POJO,并创建方法来加密/解密每个字段。 2.1. Spring Boot Starter 和加解密模块 首先,需要使用 spring-boot-starter-data-mongodb 来连接到 MongoDB: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> 然后,还需要 mongodb-crypt,以启用加密功能: <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-crypt</artifactId> <version>1.7.3</version> </dependency> 由于我们使用的是 Spring Boot,所以只需上面两个依赖就足够了。 2.2、创建 Master Key Master Key(主密钥)用于加密和解密数据。任何拥有它的人都可以读取我们的数据。因此,确保主密钥的安全至关重要。 MongoDB 建议使用 远程密钥管理服务。不过,为了简单起见,本文使用本地密钥管理器,如下: public class LocalKmsUtils { public static byte[] createMasterKey(String path) { byte[] masterKey = new byte[96]; new SecureRandom().

Spring AI Advisor 指南

1、概览 AI 驱动的应用已成为我们的新现实。我们正在广泛实现各种 RAG 应用和提示 API,并使用 LLM 创建令人印象深刻的项目。借助 Spring AI,我们可以更快、更稳定地完成这些任务。 本文将带你了解 Spring AI Advisor 这一功能,它可以为我们处理各种常规任务。 2、Spring AI Advisor 是什么? Advisors 是在 AI 应用程序中处理请求和响应的拦截器。我们可以使用它们为提示流程设置额外的功能。例如,可以建立聊天历史、排除敏感词或为每个请求添加额外的上下文。 该功能的核心组件是 CallAroundAdvisor 接口。我们通过实现该接口来创建 Advisor 链,从而影响我们的请求或响应。Advisor 流程如下图所示: 我们会将提示(prompt)发送到一个聊天模型,该模型关联了一个 Advisor 链。在发送提示之前,链上的每个 Advisor 都会执行其 before 操作。同样,在收到聊天模型的回复之前,每个 Advisor 都会调用自己的 after 操作。 3、ChatMemoryAdvisor ChatMemoryAdvisor 是一组非常有用的 Advisor 实现。我们可以使用这些 Advisor 提供与聊天提示的交流历史,从而提高聊天回复的准确性。 3.1、MessageChatMemoryAdvisor 使用 MessageChatMemoryAdvisor,我们可以通过 messages 属性提供聊天客户端调用的聊天历史记录。我们可以将所有消息保存在 ChatMemory 实现中,并控制历史记录的大小。 示例如下: @SpringBootTest(classes = ChatModel.class) @EnableAutoConfiguration @ExtendWith(SpringExtension.class) public class SpringAILiveTest { @Autowired @Qualifier("openAiChatModel") ChatModel chatModel; ChatClient chatClient; @BeforeEach void setup() { chatClient = ChatClient.

解决 Java 异常 “java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking”

1、简介 Spring Webflux 是一个非阻塞的 Web 框架,从底层开始构建,旨在利用多核、下一代处理器的优势,处理大量并发连接(既然是非阻塞框架,线程就不应该被阻塞)。 本文将带你了解在使用 Spring Webflux 时常犯的一个错误。 2、Spring Webflux 线程模型 为了更好地理解这个问题,我们需要了解 Spring Webflux 的线程模型。 在 Spring Webflux 中,一个小型工作线程池负责处理传入请求。这与 Servlet 模型不同,在 Servlet 模型中,每个请求都有一个专用线程。因此,框架会保护(隔离)这些接受(处理)请求的线程。 理解了这一点后,继续往下看。 3、通过线程阻塞了解 IllegalStateException 让我们通过一个示例来了解 Spring Webflux 中何时以及为何会出现异常:“java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread”。 以文件搜索 API 为例。该应用从文件系统读取文件,并在文件中搜索用户提供的文本。 3.1、FileService 先定义一个 FileService 类,它能以字符串形式读取文件内容: @Service public class FileService { @Value("${files.base.dir:/tmp/bael-7724}") private String filesBaseDir; public Mono<String> getFileContentAsString(String fileName) { return DataBufferUtils.read(Paths.get(filesBaseDir + "/" + fileName), DefaultDataBufferFactory.sharedInstance, DefaultDataBufferFactory.DEFAULT_INITIAL_CAPACITY) .

Java 异常:IncompatibleClassChangeError

1、概览 本文将带你了解 Java 中的 IncompatibleClassChangeError 异常,这是一种运行时异常,当 JVM 检测到类的更改与之前加载的类不兼容时就会发生。 本文将带你了解出现这个异常的原因以及解决办法。 2、IncompatibleClassChangeError 类 IncompatibleClassChangeError 是 Java 中的一种 LinkageError。该异常通常表示一个或多个依赖类出现了问题。 IncompatibleClassChangeError(不兼容类变更错误)是 LinkageError 的子类,当一个或多个从属类的类定义发生不兼容变更时会导致该异常。 注意,这是 Error 的子类,因此不应该试图 catch 这些异常,因为这意味着应用程序或运行时出现异常需要进行处理。 3、异常的产生 接下来,让我们模拟一种会导致 IncompatibleClassChangeError 的情况。 3.1、预定义一个第三方库 首先创建一个简单的库(项目),其中有一个父类 Dinosaur 和一个子类 Coelophysis,后者继承自 Dinosaur: public class Dinosaur { public void species(String sp) { if(sp == null) { System.out.println("I am a generic Dinosaur"); } else { System.out.println(sp); } } } public class Coelophysis extends Dinosaur { public void mySpecies() { species("My species is Coelophysis of the Triassic Period"); } public static void main(String[] args) { Coelophysis coelophysis = new Coelophysis(); coelophysis.

在 Java 中优雅地操纵时间

在开发时候,发现有很多需要用到时间的地方,例如记录操作的时间、比较时间判断产品是否有效等。总而言之,时间是我们业务开发必须关注、时刻注意的点。但目前工程的代码中使用了非常多时间的工具类,一会儿用 java.util.Date 记录时间,一会用 java.time.LocalDateTime 记录时间,怎么才能在 Java 中优雅的操纵时间呢,我整理了相关的概念和工具类,希望帮助大家在代码开发的过程中对对时间的使用更加优雅。 这里先写一个结论: 建议使用 java8 的时间 API,在安全性和易用性上都远高于 java.util.Date。 目前比较流行的封装 java API 的时间工具类大都基于 java.util.Date,建议在开发过程中根据业务需要基于 java.time.* 的方法封装工具类(文末给出了一个简单的实现)。 时间在计算机中的存储和展示 时间以整数的方式进行存储:时间在计算机中存储的本质是一个整数,称为 Epoch Time(时间戳),计算从 1970 年 1 月 1 日零点(格林威治时间/GMT+00:00)到现在所经历的秒数。 在 java 程序中,时间戳通常使用 long 表示毫秒数,通过 System.currentTimeMillis() 可以获取时间戳。时间戳对我们人来说是不易理解的,因此需要将其转换为易读的时间,例如,2024-10-7 20:21:59(实际上说的是本地时间),而同一时刻不同时区的人看到的本地时间是不一样,所以在时间展示的时候需要加上时区的信息,才能精准的找到对应的时刻。 时区与世界时间标准相关: 世界时间的标准在 1972 年发生了变化,但我们在开发程序的时候可以忽略 GMT 和 UTC 的差异, 因为计算机的时钟在联网的时候会自动与时间服务器同步时间。 本地时间等于我们所在(或者所使用)时区内的当地时间,它由与世界标准时间(UTC)之间的偏移量来定义。这个偏移量可以表示为 UTC- 或 UTC+,后面接上偏移的小时和分钟数。 例如:GMT+08:00 或者 UTC+08:00 表示东八区,2024-10-7 20:21:59 UTC+08:00 便可以精准的定位一个时刻。 日期 API JDK 以版本 8 为界,有两套处理日期/时间的 API。 简单的比较如下: 特性 java.util.Date java.util.Date.Calendar java.time.LocalDateTime 线程安全 ❌ ❌ ✅ 时间运算 ❌ ✅ ✅ 可读性 Tue Oct 08 00:11:16 CST 2024 易读性较低 ❌不易读 ✅ yyyy-MM-dd’T’HH:mm:ss 常量设计 需要对获取的年份(+1900)月份(0-11)进行处理 需要对获月份(0-11)进行处理 ✅ 不需要额外处理,符合常识 时间精度 精确到毫秒 精确到毫秒 精确到纳秒 时区 具体的时间调用 不 - 特性 java.

Entitymanagerfactory 和 Sessionfactory

1、简介 本文将带你了解 SessionFactory 和 EntityManagerFactory 之间的异同。 顾名思义,这两个类都是用于创建数据库通信对象的工厂类。除了创建对象,它们还提供了其他功能,帮助我们与数据库进行交互。 2、EntityManagerFactory 是什么? Java 持久化 API(JPA)是 Java 应用管理持久化数据的规范。它提供了一种与关系数据库交互的标准方式。EntityManager(实体管理器)作为 JPA 的核心接口,用于与持久化上下文交互并管理实体的生命周期。它为基本的 CRUD 操作提供了具有方法的轻量级实例。 EntityManagerFactory 是一个 JPA 接口,用于创建 EntityManager 实例,以线程安全的方式与持久化上下文(Persistence Context)交互。 2.1、示例 首先,先定义实体: @Entity(name = "persons") public class Person { @Id @GeneratedValue(strategy= GenerationType.IDENTITY) private Integer id; private String name; private String email; // Getter / Setter 省略 } 设置配置有多种方法,这里使用 persistence.xml 配置文件的方法。首先,需要在 resource/META-INF 文件夹中创建该配置文件,并定义连接配置: <persistence-unit name="com.baeldung.sfvsemf.persistence_unit" transaction-type="RESOURCE_LOCAL"> <description>Persistence Unit for SessionFactory vs EntityManagerFactory code example</description> <class>com.baeldung.sfvsemf.entity.Person</class> <exclude-unlisted-classes>true</exclude-unlisted-classes> <properties> <property name="hibernate.

通过 JDBC 驱动连接 Oracle 数据库

1、概览 Oracle 数据库是最流行的关系数据库之一。本文将带你了解如何使用 JDBC 驱动连接到 Oracle 数据库。 2、数据库 首先,我们需要一个数据库。如果没有,则可以从 Oracle Database Software Downloads 下载并安装免费版本,或者使用 Oracle Database Container Images 上的 Docker 镜像。 本文中使用的是 Oracle Database 23ai (23.5.0) 的 Docker 镜像。 3、Maven 依赖 数据库就绪后,在项目中添加 Oracle 的 JDBC 驱动依赖,本文使用 ojdbc11 连接 Oracle 23ai: <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc11</artifactId> <version>23.5.0.24.07</version> </dependency> ojdbc11 的最新版本可在 Central Maven Repository 中找到。该依赖项要求 Java 11 或更高版本,是较新版本 Java 的推荐驱动。 为了向后兼容,ojdbc8 可用于 Java 8。也推荐使用 ojdbc10 作为 Oracle 19c 的驱动程序。 4、连接到数据库 首先,创建一个 OracleDataSource(Oracle 数据源接口的实现)。这比使用 DriverManager 更好,因为 DataSource 更具可扩展性,也更易于设置。

解决 Spring JPA 异常:“Unable to Locate Attribute with the Given Name”

1、简介 Spring 为程序员简化 Java 应用程序中的数据库交互提供了一个最强大的框架,那就是 Spring JPA(Java Persistence API)。它为 JPA 提供了一个稳定的抽象。 然而,尽管使用方便,开发人员还是经常会遇到一些错误,而这些错误的排查和解决都非常具有迷惑性。其中一个常见问题就是 “Unable to Locate Attribute with the Given Name” 错误。 本文将带你了解 Spring JPA 出现 “Unable to Locate Attribute with the Given Name” 异常的原因以及解决办法。 2、案例 我们生产了一个可穿戴的小工具。经过最近的一项调查,我们的营销团队发现,在我们的平台上按传感器类型、价格和受欢迎程度对产品进行分类,可以突出最受欢迎的产品,从而帮助客户做出更好的购买决策。 3、添加 Maven 依赖 我们使用 H2 内存数据库在项目中创建一个可穿戴设备表,并将样本数据填充到该表中,以便在接下来的测试中使用。 首先,添加以下 Maven 依赖: <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.2.224</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>2.7.11</version> </dependency> 4、应用配置 在 src/main/resources 文件夹中,创建包含以下配置内容的 application-h2.properties: # H2 配置 hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.hbm2ddl.auto=create-drop # Spring Datasource URL spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 在 src/main/resources 文件夹中创建名为 testdata.