教程

Spring Data JPA 检索最大值(Max Value)

1、简介 本文将带你了解如何使用 Spring Data JPA 检索数据列中的最大值(Max Value)。 2、示例 首先,添加 spring-boot-starter-data-jpa 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> 然后,定义一个简单的 Employee 实体表示员工: @Entity public class Employee { @Id @GeneratedValue private Integer id; private String name; private Long salary; // 工资 // 构造函数、Getter、Setter 方法省略 } 接下来,看看有哪些方法可以检索出所有员工中薪水(salary)列的最大值。 3、使用 Repository 的派生查询 Spring Data JPA 提供了一个强大的机制,可以使用 Repository 方法来定义自定义查询。其中之一是派生查询,它允许我们通过声明方法名来实现SQL查询。 创建 Employee 实体类的 Repository 接口: public interface EmployeeRepository extends JpaRepository<Employee, Integer> { Optional<Employee> findTopByOrderBySalaryDesc(); } 如上,我们实现了一个方法 findTopByOrderBySalaryDesc,该方法使用查询派生机制生成相应的 SQL。根据方法名,它将按照工资(salary)降序对所有员工进行排序,然后返回第一个员工,即工资最高的员工。 该方法会返回一个加载了所有属性的实体。如果我们只想检索一个工资(salary)值,可以使用投影查询: 创建 EmployeeSalary 投影接口:

在 Golang 中实现类似于 Spring 中的模板事务

事务(TRANSACTION),是指一组操作的集合,这些操作要么全部成功,要么全部失败。其目的是在出现错误、系统崩溃或其他意外情况下,保证数据的一致性和完整性。 事务通常具有以下四个重要的特性,这些特性被统称为 ACID 属性: Atomicity(原子性): 定义: 事务中的所有操作要么全部完成,要么全部不完成,任何一个操作失败都会导致整个事务的失败,并且事务的所有操作都会被回滚(撤销)。 示例: 银行转账操作,如果从一个账户扣款后无法在另一个账户中存款,那么整个操作将回滚,不会执行任何更改。 Consistency(一致性): 定义: 事务只能把数据库从一种一致状态转换到另一种一致状态。在事务开始之前和结束之后,数据库的完整性约束没有被破坏。 示例: 在一个事务中插入数据时,如果插入的数据违反了数据库的完整性约束(例如唯一约束),那么这个事务将失败,数据库将保持一致状态。 Isolation(隔离性): 定义: 事务的执行是隔离的,多个事务并发执行时,一个事务的执行不会受到其他事务的干扰。隔离性确保了并发事务的执行结果与按顺序执行的结果相同。 示例: 两个用户同时购买同一件商品,隔离性确保每个用户看到的库存是正确的,避免超卖的情况。 Durability(持久性): 定义: 一旦事务提交,其结果将永久保存在数据库中,即使系统崩溃也不会丢失。 示例: 即使在事务提交后立即发生系统崩溃,事务的结果也会保存在数据库中,重启系统后数据依然存在。 以 MYSQL 关系型数据库为例,事务的使用如下: -- 开始事务 BEGIN TRANSACTION; -- TODO 执行业务 1 -- TODO 执行业务 2 -- TODO 执行业务 3 -- .... -- 提交事务 COMMIT; -- 或者,回滚事务 ROLLBACK; 其中,BEGIN TRANSACTION、COMMIT 以及 ROLLBACK 都是事务固定的模板代码,当代的大多数框架都会自动帮我们进行处理。 Spring 对事务的支持 Spring 对关系型数据库中的事务提供强大的支持,包括声明式事务、TransactionTemplate 模板事务等等。 @Transactional 声明式事务 实际开发中,最常用的就是通过 @Transactional 注解来声明事务方法。事务方法会在执行开始前自动开始事务,在方法结束后自动提交事务,在执行过程中如果遇到异常则自动回滚事务。 import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Transactional // 为 public 方法开启事务 public class FooService { public void service () { // TODO 在事务中执行业务 } } 在类上注解 @Transactional,则会为当前类中所有 public 方法开启声明式事务。也可以单独注解在方法上,则会覆盖类上的 @Transactional 定义。

Java 中的向上转型和向下转型

1、简介 了解如何处理 Java 类型层次结构中的对象对于编写灵活和可维护的代码至关重要。在这个领域中,两个基本概念是向上转型(Upcasting)和向下转型(Downcasting)。 本文将带你深入了解这些概念,探索它们之间的区别,以及它们在 Java 中的原理。 2、Java 中的类型转换 Java 是一种面向对象的编程语言(Object Oriented Programming,OOP),允许在其类型系统中将一种类型转换为另一种类型。转换是将一种类类型的引用转换为另一种类类型的过程。具体来说,Java 中主要有两种类型的转换:向上转型(Upcasting)和向下转型(Downcasting)。 假设我们有一个类的层次结构,其中 Dog 是 Animal 的子类。下图显示了向上转型(Upcasting)和向下转型(Downcasting)在这个层次结构中的工作原理: 向上转型(Upcasting)箭头从 Dog 类移动到 Animal 类,显示了子类引用(Dog)如何泛化为父类引用(Animal)。而,向下转型(Downcasting)箭头从 Animal 类返回 Dog 类,显示父类引用(Animal)如何再次被指定为子类引用(Dog)。 但是,如果尝试使用 Animal 引用实例化 Dog 对象,则会因类型不兼容而导致编译错误。 Dog dog = new Animal(); //异常 ClassCastException 3、Java 向上转型 向上转型(Upcasting)是指将子类引用转换为父类引用。这种类型的转换是隐式的,在处理多态性时经常使用它。 向上转型(Upcasting)允许我们将一个子类的对象当作一个父类的对象来处理: class Animal { public void makeSound() { System.out.println("Animal sound"); } } class Dog extends Animal { public void makeSound() { System.out.println("Bark"); } public void fetch() { System.

Jackson 序列化和反序列化 java.sql.Blob

1、简介 本文将带你了解如何使用 Jackson 序列化和反序列化 java.sql.Blob 对象。 java.sql.Blob 表示 Java 中的二进制大对象(Binary Large Object,Blob),可以存储大量二进制数据。在使用 Jackson 处理 JSON 序列化和反序列化时,处理 Blob 对象可能比较棘手,因为 Jackson 并不直接支持它们。不过,我们可以创建自定义的序列化器(Serializer)和反序列化器(Deserializer)来处理 Blob 对象。 2、依赖和示例项目 首先,在 pom.xml 中添加 jackson-databind 依赖: <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency> 然后,创建一个简单的 User POJO,其中包含 id、name 和 Blob 类型的 profilePicture(图片数据): public class User { private int id; private String name; private Blob profilePicture; // 构造函数,Getter/Setter 省略 } 3、定义 Blob 序列化器 定义一个序列化器(Serializer),将 User 的 profilePicture 属性转换为 Base64 编码的二进制字符串: @JacksonStdImpl public class SqlBlobSerializer extends JsonSerializer<Blob> { @Override public void serialize(Blob value, JsonGenerator gen, SerializerProvider serializers) throws IOException { try { byte[] blobBytes = value.

使用 Spring WebClient 和 WireMock 进行集成测试

1、简介 Spring WebClient 是一款非阻塞、响应式的 HTTP 客户端,而 WireMock 是一个强大的用于模拟基于 HTTP 的 API 的工具。 2、依赖和示例 首先,需要在 Spring Boot 项目中添加必要的依赖。 在 pom.xml 中添加 spring-boot-starter-flux(WebClient) 和 spring-cloud-starter-wiremock(WireMock Server)依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-contract-wiremock</artifactId> <version>4.1.2</version> <scope>test</scope> </dependency> 假设,我们的应用需要调用外部天气 API,以获取给定城市的天气数据。 定义 WeatherData POJO: public class WeatherData { private String city; private int temperature; private String description; // 构造函数、Getter/Setter 方法省略 我们要使用 WebClient 和 WireMock 进行集成测试,以测试这个功能。 3、使用 WireMock API 进行集成测试 首先用 WireMock 和 WebClient 设置 Spring Boot 测试类:

Spring Security 检测密码是否泄露

1、概览 在构建处理敏感数据的 Web 应用时,确保用户密码的安全性非常重要。密码安全的一个重要方面是检查密码是否泄露,这通常是由于密码出现在 数据泄露事件 中。 Spring Security 6.3 引入了一项新功能,让我们可以轻松检查密码是否被已泄露。 本文将带你了解 Spring Security 中新的 CompromisedPasswordChecker API 以及如何将其集成到 Spring Boot 应用中。 2、密码泄露 密码泄露是指在数据泄露事件中暴露的密码,使其容易受到未经授权的访问。攻击者通常在凭证填充和密码填充攻击中使用这些泄露的密码,在多个网站上使用泄露的用户名-密码对,或在多个账户上使用通用密码。 要降低这种风险,关键是要在创建账户前检查用户密码是否泄露。 同样重要的是要注意,以前有效的密码可能会随着时间的推移而泄露,因此建议不仅在创建账户时,而且在登录过程中或任何允许用户更改密码的过程中都要检查密码是否泄露。如果登录尝试因检测到密码泄露而失败,可以提示用户重设密码。 3、CompromisedPasswordChecker Spring Security 提供了一个简单的 CompromisedPasswordChecker 接口,用于检查密码是否被泄露: public interface CompromisedPasswordChecker { CompromisedPasswordDecision check(String password); } 该接口只暴露了一个 check() 方法,该方法将密码作为输入,并返回一个 CompromisedPasswordDecision 的实例,表明密码是否已被破解/泄露。 check() 方法需要明文密码,因此必须在使用 PasswordEncoder 加密密码之前调用该方法。 3.1、配置 CompromisedPasswordChecker Bean 要在应用中启用密码泄露检查,需要声明 CompromisedPasswordChecker 类型的 Bean: @Bean public CompromisedPasswordChecker compromisedPasswordChecker() { return new HaveIBeenPwnedRestApiPasswordChecker(); } HaveIBeenPwnedRestApiPasswordChecker 是 Spring Security 提供的 CompromisedPasswordChecker 的默认实现。

Spring AI 和 Open AI 入门

Open AI 和 Spring AI 简介 当 OpenAI 发布 ChatGPT 时,它在全球掀起了一场风暴。这是第一次有语言模型能够根据提示生成类似人类的回答。此后,OpenAI 又发布了其他几个模型,包括可以根据文字提示生成图像的 DALL-E。 Spring AI 是一个 Java 库,为与 LLM 模型交互提供了一个简单易用的接口。Spring AI 提供了与各种 LLM(如 Open AI、Azure Open AI、Hugging Face、Google Vertex、Ollama、Amazon Bedrock 等)交互的高级抽象。 本文将带你了解如何使用 Spring AI 与 Open AI 能进行交互。 首先,需要在 OpenAI 上创建账户并获取 API Key。 访问 OpenAI Platform 并创建账户。 在控制面板中,点击左侧导航菜单中的 API Keys,创建一个新的 API Key。 如果你是新用户,你可能会获得一些免费点数来使用 OpenAI API。否则,你需要购买点数才能使用 OpenAI API。 获得 API KEY 后,把它添加到环境变量 OPENAI_API_KEY 中。 export OPENAI_API_KEY=<your-api-key> 创建 Spring AI 项目 使用 Spring Initializr 创建一个新的 Spring Boot 项目。

Tomcat 不停机升级 Web 应用

1、概览 Apache Tomcat,简称 Tomcat,是 Jakarta Servlet 规范的开源实现。它作为 Web 服务器接收 HTTP 或 WebSocket 请求,并调用负责的 Servlet 来处理请求。 本文将带你了解 Tomcat 中的并行部署(Parallel Deployment),以及如何实现不停机升级 Web 应用。 2、Tomcat 部署模式 我们可以通过两种方式使用 Apache Tomcat 为 Web 应用提供服务。第一种方法是将 Tomcat 程序嵌入 Java 应用本身。或者,可以将 Apache Tomcat 作为一个专用的 Web 服务器进程运行,为一个或多个 Web 应用提供服务。在这种模式下,开发人员会将 Web 应用打包成 WAR (Web Application Archive,Web 应用归档)包。然后,Web 服务器管理员会将 Web 应用部署到 Tomcat Web 服务器上。 独立的 Tomcat 部署模式现在不怎么流行了,但是也有好处。多个不同的 Web 应用使用同一个 Tomcat 服务器会节省一定的资源。 将 Tomcat 作为独立 Web 服务器运行时,需要了解如何对运行中的 Web 应用进行不停机重新部署。与通常将此任务委托给 Kubernetes 等外部协调器的容器化 Web 应用相反,Tomcat 中的部署依赖于 Tomcat 服务器,以最大限度地减少 Web 应用升级的停机时间。

如何控制 Spring Bean 的加载顺序?

先说结论,使用 @Order 注解或者是实现 Ordered 接口并不能控制 Bean 的加载顺序。 一、@Order 注解和 Ordered 接口 在 Spring 框架中,@Order 是一个非常实用的元注解,它位于 spring-core 包下,主要用于控制某些特定上下文(Context)中组件的执行顺序或排序,但它并不直接控制 Bean 的初始化顺序。 1.1、用途 @Order 注解或者是 Ordered 接口,常见的用途主要是两种: 定义执行顺序:当多个组件(如 Interceptor、Filter、Listrner 等)需要按照特定的顺序执行时,@Order 注解可以用来指定这些组件的执行优先级。数值越小,优先级越高,相应的组件会更早被执行或被放置在集合的前面(@Order 注解接受一个整数值,这个值可以是负数、零或正数。Spring 框架提供了 Ordered.HIGHEST_PRECEDENCE(默认最低优先级)和 Ordered.LOWEST_PRECEDENCE(默认最高优先级)常量,分别对应于 Integer.MIN_VALUE 和 Integer.MAX_VALUE,可以方便地设定优先级。 集合排序:当相同类型的组件被自动装配到一个集合中时,@Order 注解会影响它们在这个集合中的排列顺序。 1.2、使用场景 经典的使用场景如下。 拦截器的排序 在 Spring MVC 中,可以使用 @Order 来控制拦截器(Interceptor)的执行顺序。 Spring Security Filter 在 Spring Security 中,过滤器链(Filter Chain)的顺序通过 @Order 来定义,确保正确的安全处理流程。 // HttpSecurity 的 performBuild 方法 @Override protected DefaultSecurityFilterChain performBuild() { // 对 Filter 进行排序 this.

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 信息如下: