Initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
---
|
||||
title: IDEA 2025.2 Maven中文乱码解决方案
|
||||
created: 2025-08-06
|
||||
source: Cherry Studio
|
||||
tags: []
|
||||
---
|
||||
|
||||
# IntelliJ IDEA 2025.2 中 Maven 控制台中文乱码终极解决方案
|
||||
|
||||
升级到 **IntelliJ IDEA 2025.2** 后,发现:**Maven 构建日志中的中文变成了乱码**。
|
||||
|
||||
## 问题原因
|
||||
|
||||
从 **IntelliJ IDEA 2025.2 开始**,Maven 的执行方式发生了变化:
|
||||
|
||||
> **不再直接调用 `java.exe`,而是通过 `cmd.exe /c` 来启动 Maven**,可以从第一行看出来。
|
||||
|
||||
在中文版 Windows 系统中,`cmd.exe` 默认使用 **GBK 编码(代码页 936)**,而项目和源码通常是 **UTF-8 编码**,这就导致了编码不一致,从而出现中文乱码。
|
||||
|
||||
即使你在项目中设置了:
|
||||
|
||||
- File Encoding 为 UTF-8
|
||||
- Properties 中指定 `-Dfile.encoding=UTF-8`
|
||||
|
||||
也**可能无效**,因为 Maven 是通过 `cmd` 启动的,JVM 仍未强制使用 UTF-8。
|
||||
|
||||
## 解决方案:通过环境变量强制 UTF-8
|
||||
|
||||
我们可以通过设置 `JAVA_TOOL_OPTIONS` 环境变量,让 JVM **无论在哪种环境下都使用 UTF-8 编码**。
|
||||
|
||||
1. 打开设置:`Ctrl + Alt + S` 或菜单 → File > Settings
|
||||
2. 进入路径:Build, Execution, Deployment > Build Tools > Maven > Runner
|
||||
3. 设置环境变量:Environment variables,Name 为 `JAVA_TOOL_OPTIONS`,Value 为 `-Dfile.encoding=UTF-8`
|
||||
4. 保存并重启项目或 IDE
|
||||
@@ -0,0 +1,57 @@
|
||||
# IntelliJ IDEA
|
||||
|
||||
在 IntelliJ IDEA 中,项目(Project)、模块(Module)、库(Library)、Facet、工件(Artifact)是项目结构中不同的组成部分,它们有各自的用途,帮助开发者组织和管理代码、依赖、配置等。以下是每个概念的解释及其作用:
|
||||
|
||||
### 1. **项目(Project)**
|
||||
|
||||
**定义**: 项目是 IntelliJ IDEA 中的最高级别的结构,是代码、资源和配置的集合。项目可以包含一个或多个模块。
|
||||
|
||||
**作用**:
|
||||
- 一个项目通常代表一个应用程序或一组应用程序。
|
||||
- 项目可以包含代码、文件夹、库、配置文件等。
|
||||
- 可以配置全局设置,比如版本控制、代码样式等。
|
||||
|
||||
### 2. **模块(Module)**
|
||||
|
||||
**定义**: 模块是项目中的一个功能单元或一个代码库。每个模块可以独立编译,并且可以包含源代码、测试代码、依赖库等。
|
||||
|
||||
**作用**:
|
||||
- 模块化开发中的核心单元,每个模块可以有自己的类路径、编译设置和依赖。
|
||||
- 一个项目可以包含多个模块,模块之间可以共享代码或资源。
|
||||
- 不同的模块可以有不同的编译目标,比如有的模块可以是 Java 应用,有的模块可以是 Web 应用,甚至是 Android 应用。
|
||||
|
||||
### 3. **库(Library)**
|
||||
|
||||
**定义**: 库是一个或多个类的集合,通常由 `.jar` 文件、类文件或其他外部依赖组成。库可以用于模块中,也可以在多个项目中共享使用。
|
||||
|
||||
**作用**:
|
||||
- 库包含外部依赖,比如第三方的 `.jar` 文件(如 `Spring`, `Hibernate` 等)。
|
||||
- 库可以添加到项目中或模块中,来扩展其功能或引入额外的 API。
|
||||
- 可以定义全局库(整个项目共享)或模块库(仅在单个模块内使用)。
|
||||
|
||||
### 4. **Facet**
|
||||
|
||||
**定义**: Facet 是一个模块的设置组件,用于处理特定类型的框架或技术支持(如 Spring、Maven、Web 开发等)的相关配置。
|
||||
|
||||
**作用**:
|
||||
- 每个模块可以有多个 Facet,用于支持不同的框架。
|
||||
- 它帮助开发者配置和管理特定技术栈的开发环境,比如配置 Spring 框架、JavaEE 规范等。
|
||||
- Facet 提供了相应框架的工具支持,比如配置文件的自动识别、代码补全等。
|
||||
|
||||
### 5. **工件(Artifact)**
|
||||
|
||||
**定义**: 工件是可以从项目模块生成的最终产品,比如编译后的 `.jar` 文件、`.war` 文件等。
|
||||
|
||||
**作用**:
|
||||
- 工件定义了如何打包项目的输出,通常是生成可部署的文件,比如 Java 应用的 `.jar` 文件或 Web 应用的 `.war` 文件。
|
||||
- 可以配置不同的打包方式,比如将模块的类文件、依赖库打包到一个文件中,或生成多个不同的部署工件。
|
||||
- 工件配置决定了如何将代码发布或部署到服务器或其他目标环境。
|
||||
|
||||
### 整体结构理解:
|
||||
|
||||
- **项目(Project)** 是最外层的容器,可以包含多个 **模块(Module)**。每个模块可以独立工作,并且有自己的依赖和配置。
|
||||
- **库(Library)** 是提供给模块的外部依赖,可以是项目级别的或模块级别的。
|
||||
- **Facet** 是帮助模块支持特定框架或技术的配置组件。
|
||||
- **工件(Artifact)** 是从模块中生成的可部署的输出文件,定义了如何打包和发布应用。
|
||||
|
||||
通过合理组织这些结构,可以使开发过程更加清晰、模块化,并且易于维护和扩展。
|
||||
@@ -0,0 +1,28 @@
|
||||
```java
|
||||
@Query(value = "select ei.enterprise_name as enterpriseName, ei.enterprise_cp_id as enterpriseCpId, ssdc.domain as domain, ssdc.product_id as productId " +
|
||||
"from self_service_domain_config ssdc " +
|
||||
"left join enterprise_info ei on ssdc.tenant_id = ei.enterprise_id " +
|
||||
"where ei.source = 'BSS' and ssdc.state not in (20, 7, 14, 15, 16, 19, 21, 22, 29, 30, 31, 32, 33, 5)",
|
||||
nativeQuery = true)
|
||||
List<EnterpriseNameCpIdDomainProductId> findEnterpriseInfo();
|
||||
|
||||
@RequestMapping(value = "/123456", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public int testtttttt() {
|
||||
List<EnterpriseNameCpIdDomainProductId> enterpriseInfo = selfServiceDomainConfigDao.findEnterpriseInfo();
|
||||
enterpriseInfo.forEach(result -> {
|
||||
System.out.println("Enterprise Name: " + result.getEnterpriseName());
|
||||
System.out.println("Enterprise CpId: " + result.getEnterpriseCpId());
|
||||
System.out.println("Domain: " + result.getDomain());
|
||||
System.out.println("Product Id: " + result.getProductId());
|
||||
});
|
||||
return enterpriseInfo.size();
|
||||
}
|
||||
|
||||
public interface EnterpriseNameCpIdDomainProductId {
|
||||
String getEnterpriseName();
|
||||
String getEnterpriseCpId();
|
||||
String getDomain();
|
||||
String getProductId();
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,425 @@
|
||||
Spring Data JPA 提供了多种形式来操作数据库,旨在简化开发者与数据库的交互。以下是 Spring Data JPA 中操作数据库的主要形式,并附上示例说明。这些形式主要围绕其核心抽象——`Repository`(仓库)接口展开,同时结合不同的查询方式和功能。
|
||||
|
||||
### 1. **通过方法名自动生成查询**
|
||||
|
||||
这是 Spring Data JPA 最常用的方式,开发者只需在 Repository 接口中定义方法,方法名遵循特定的命名约定,Spring Data JPA 会自动根据方法名生成查询语句。
|
||||
|
||||
- **原理**:Spring Data JPA 会解析方法名,根据命名规则(如 `findBy`, `existsBy`, `countBy` 等)生成对应的 SQL 或 JPQL 查询。
|
||||
- **适用场景**:适用于简单的查询需求,例如按字段查找、统计、判断是否存在等。
|
||||
- **示例**:
|
||||
假设有一个实体类 `User`:
|
||||
|
||||
```java
|
||||
package com.example.domain;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
@Entity
|
||||
public class User {
|
||||
@Id
|
||||
private Long id;
|
||||
private String name;
|
||||
private Integer age;
|
||||
|
||||
// getters and setters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(Integer age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
// 查找名字为指定值的用户
|
||||
User findByName(String name);
|
||||
|
||||
// 查找年龄大于指定值的用户列表
|
||||
List<User> findByAgeGreaterThan(Integer age);
|
||||
|
||||
// 判断是否存在指定名字的用户
|
||||
boolean existsByName(String name);
|
||||
|
||||
// 统计指定年龄的用户数量
|
||||
long countByAge(Integer age);
|
||||
}
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public void test() {
|
||||
// 查找名字为 "Alice" 的用户
|
||||
User user = userRepository.findByName("Alice");
|
||||
|
||||
// 查找年龄大于 20 的用户
|
||||
List<User> users = userRepository.findByAgeGreaterThan(20);
|
||||
|
||||
// 判断是否存在名字为 "Bob" 的用户
|
||||
boolean exists = userRepository.existsByName("Bob");
|
||||
|
||||
// 统计年龄为 25 的用户数量
|
||||
long count = userRepository.countByAge(25);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. **使用 `@Query` 注解自定义查询**
|
||||
|
||||
当方法名自动生成的查询无法满足需求时,可以使用 `@Query` 注解自定义 JPQL(Java Persistence Query Language)或原生 SQL 查询。
|
||||
|
||||
- **原理**:通过 `@Query` 注解直接指定查询语句,Spring Data JPA 不会解析方法名,而是使用注解中的查询逻辑。
|
||||
- **适用场景**:适用于复杂的查询逻辑,例如多表关联、聚合查询、自定义条件等。
|
||||
- **示例**:
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
// 使用 JPQL 查询名字包含指定关键字的用户
|
||||
@Query("SELECT u FROM User u WHERE u.name LIKE %:keyword%")
|
||||
List<User> findUsersByNameContaining(@Param("keyword") String keyword);
|
||||
|
||||
// 使用原生 SQL 查询
|
||||
@Query(value = "SELECT * FROM user WHERE age > :age", nativeQuery = true)
|
||||
List<User> findUsersOlderThan(@Param("age") Integer age);
|
||||
}
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public void test() {
|
||||
// 查找名字包含 "li" 的用户
|
||||
List<User> usersWithKeyword = userRepository.findUsersByNameContaining("li");
|
||||
|
||||
// 查找年龄大于 30 的用户(使用原生 SQL)
|
||||
List<User> olderUsers = userRepository.findUsersOlderThan(30);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **使用投影接口(Projection)**
|
||||
|
||||
投影接口用于只查询实体中的部分字段,避免加载不需要的数据,从而优化性能。
|
||||
|
||||
- **原理**:定义一个接口,包含需要查询字段的 getter 方法,Spring Data JPA 会将查询结果映射到这个接口。
|
||||
- **适用场景**:适用于只需要部分字段的场景,例如列表展示只需要 `id` 和 `name`,而不需要其他字段。
|
||||
- **示例**:
|
||||
定义投影接口:
|
||||
|
||||
```java
|
||||
package com.example.projection;
|
||||
|
||||
public interface UserProjection {
|
||||
Long getId();
|
||||
String getName();
|
||||
}
|
||||
```
|
||||
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import com.example.projection.UserProjection;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
// 查询所有用户的 id 和 name
|
||||
List<UserProjection> findAllProjectedBy();
|
||||
|
||||
// 查询指定年龄的用户的 id 和 name
|
||||
List<UserProjection> findByAge(Integer age);
|
||||
}
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public void test() {
|
||||
// 查询所有用户的 id 和 name
|
||||
List<UserProjection> projections = userRepository.findAllProjectedBy();
|
||||
|
||||
// 查询年龄为 25 的用户的 id 和 name
|
||||
List<UserProjection> ageProjections = userRepository.findByAge(25);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. **使用 DTO 投影(结合 `@Query`)**
|
||||
|
||||
除了投影接口,也可以在 `@Query` 中直接映射查询结果到 DTO(Data Transfer Object)或 VO(View Object)类。
|
||||
|
||||
- **原理**:在 `@Query` 中使用 `new` 关键字直接构造 DTO 对象,将查询结果映射到 DTO 的构造函数。
|
||||
- **适用场景**:适用于需要返回自定义视图对象,且字段可能来自多表或复杂计算的场景。
|
||||
- **示例**:
|
||||
定义 DTO 类:
|
||||
|
||||
```java
|
||||
package com.example.dto;
|
||||
|
||||
public class UserDTO {
|
||||
private Long id;
|
||||
private String name;
|
||||
|
||||
public UserDTO(Long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
}
|
||||
```
|
||||
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import com.example.dto.UserDTO;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
@Query("SELECT new com.example.dto.UserDTO(u.id, u.name) FROM User u WHERE u.age > :age")
|
||||
List<UserDTO> findUserDTOsByAgeGreaterThan(Integer age);
|
||||
}
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public void test() {
|
||||
// 查询年龄大于 20 的用户的 id 和 name,返回 DTO
|
||||
List<UserDTO> userDTOs = userRepository.findUserDTOsByAgeGreaterThan(20);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. **使用分页和排序(Paging and Sorting)**
|
||||
|
||||
Spring Data JPA 提供了内置的分页和排序功能,适用于大数据量场景下的分页展示。
|
||||
|
||||
- **原理**:通过 `Pageable` 对象指定分页参数和排序规则,Spring Data JPA 自动处理分页查询和结果封装。
|
||||
- **适用场景**:适用于列表分页展示、数据量大时分批加载。
|
||||
- **示例**:
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
// 分页查询所有用户
|
||||
Page<User> findAll(Pageable pageable);
|
||||
|
||||
// 分页查询指定年龄的用户
|
||||
Page<User> findByAge(Integer age, Pageable pageable);
|
||||
|
||||
// 查询并排序(不分页)
|
||||
List<User> findByAgeOrderByNameAsc(Integer age);
|
||||
}
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public void test() {
|
||||
// 创建分页参数,第 1 页,每页 10 条,按 name 升序排序
|
||||
Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending());
|
||||
|
||||
// 分页查询所有用户
|
||||
Page<User> userPage = userRepository.findAll(pageable);
|
||||
|
||||
// 获取查询结果
|
||||
List<User> users = userPage.getContent();
|
||||
long totalElements = userPage.getTotalElements();
|
||||
int totalPages = userPage.getTotalPages();
|
||||
|
||||
// 分页查询年龄为 25 的用户
|
||||
Page<User> agePage = userRepository.findByAge(25, pageable);
|
||||
|
||||
// 查询年龄为 25 的用户并按名字升序排序(不分页)
|
||||
List<User> sortedUsers = userRepository.findByAgeOrderByNameAsc(25);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. **使用 Specification 或 Criteria 查询**
|
||||
|
||||
Spring Data JPA 提供了 `JpaSpecificationExecutor` 接口,允许开发者动态构建复杂的查询条件(类似 Hibernate 的 Criteria API)。
|
||||
|
||||
- **原理**:通过实现 `Specification` 接口,动态拼接查询条件,适用于运行时根据用户输入构建查询。
|
||||
- **适用场景**:适用于动态查询条件,例如搜索表单中用户可以选择多个过滤条件。
|
||||
- **示例**:
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
|
||||
}
|
||||
```
|
||||
|
||||
定义动态查询逻辑:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public List<User> searchUsers(String name, Integer minAge) {
|
||||
Specification<User> spec = (root, query, criteriaBuilder) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
if (name != null && !name.isEmpty()) {
|
||||
predicates.add(criteriaBuilder.like(root.get("name"), "%" + name + "%"));
|
||||
}
|
||||
if (minAge != null) {
|
||||
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("age"), minAge));
|
||||
}
|
||||
|
||||
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
|
||||
return userRepository.findAll(spec);
|
||||
}
|
||||
```
|
||||
|
||||
### 7. **使用预定义的 CRUD 方法**
|
||||
|
||||
Spring Data JPA 提供了 `CrudRepository` 和 `JpaRepository` 接口,内置了许多常用的 CRUD 方法(如 `save`, `findById`, `delete` 等),可以直接使用。
|
||||
|
||||
- **原理**:这些方法由 Spring Data JPA 自动实现,无需开发者额外定义。
|
||||
- **适用场景**:适用于基本的增删改查操作。
|
||||
- **示例**:
|
||||
定义 Repository 接口:
|
||||
|
||||
```java
|
||||
package com.example.repository;
|
||||
|
||||
import com.example.domain.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
}
|
||||
```
|
||||
|
||||
使用方式:
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
public void test() {
|
||||
// 保存用户
|
||||
User user = new User();
|
||||
user.setName("Alice");
|
||||
user.setAge(25);
|
||||
userRepository.save(user);
|
||||
|
||||
// 根据 ID 查询用户
|
||||
Optional<User> optionalUser = userRepository.findById(1L);
|
||||
if (optionalUser.isPresent()) {
|
||||
User foundUser = optionalUser.get();
|
||||
// 处理 foundUser
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
userRepository.deleteById(1L);
|
||||
|
||||
// 查询所有用户
|
||||
List<User> allUsers = userRepository.findAll();
|
||||
}
|
||||
```
|
||||
|
||||
### 8. **使用原生 SQL 查询(Native Query)**
|
||||
|
||||
除了 JPQL,还可以通过 `@Query` 注解使用原生 SQL 查询,直接操作数据库。
|
||||
|
||||
- **原理**:设置 `nativeQuery = true`,Spring Data JPA 会将查询作为原生 SQL 执行。
|
||||
- **适用场景**:适用于需要特定数据库功能或性能优化的复杂查询。
|
||||
- **示例**:
|
||||
|
||||
```java
|
||||
@Query(value = "SELECT id, name FROM user WHERE age > :age", nativeQuery = true)
|
||||
List<Object[]> findUserDataByAge(@Param("age") Integer age);
|
||||
```
|
||||
|
||||
### 总结
|
||||
|
||||
Spring Data JPA 操作数据库的主要形式包括:
|
||||
|
||||
1. **方法名自动生成查询**:通过命名约定生成简单查询。
|
||||
2. **@Query 注解自定义查询**:支持 JPQL 和原生 SQL,适用于复杂查询。
|
||||
3. **投影接口(Projection)**:只查询部分字段,优化性能。
|
||||
4. **DTO 投影**:结合 `@Query` 将结果映射到自定义 DTO。
|
||||
5. **分页和排序**:内置分页和排序功能,处理大数据量。
|
||||
6. **Specification/Criteria 查询**:动态构建查询条件,适用于搜索场景。
|
||||
7. **预定义 CRUD 方法**:直接使用内置的增删改查方法。
|
||||
8. **原生 SQL 查询**:直接使用数据库特定的 SQL 语句。
|
||||
|
||||
这些方式覆盖了从简单到复杂的各种数据库操作需求,开发者可以根据具体场景选择合适的方式。希望这些示例能帮助你更好地理解和应用 Spring Data JPA!如果有进一步问题,欢迎继续提问。
|
||||
@@ -0,0 +1,53 @@
|
||||
Java 是基于 Smalltalk 语言开发
|
||||
|
||||
# 基本数据类型
|
||||
|
||||
一般会学到 Java 中的基本数据类型为四类八种,On Java 中有 9 中基本数据类型的说法,加入了 void。
|
||||
|
||||
| 分类 | 基本类型 | 大小 | 最小值 | 最大值 | 包装类 |
|
||||
| ---- | ------- | -------- | -------------------------- | ------------------------- | --------- |
|
||||
| 布尔类型 | boolean | 没有固定大小 | false | true | Boolean |
|
||||
| 字符类型 | char | 16 位(2 字节) | 0 | 65,535 | Character |
|
||||
| 整数类型 | byte | 8 位(1 字节) | -128 | 127 | Byte |
|
||||
| 整数类型 | short | 16 位(2 字节) | -32,768 | 32,767 | Short |
|
||||
| 整数类型 | int | 32 位(4 字节) | -2,147,483,648 | 2,147,483,647 | Integer |
|
||||
| 整数类型 | long | 64 位(8 字节) | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 | Long |
|
||||
| 浮点型 | float | 32 位(4 字节) | 1.4e-45 | 3.4028235e38 | Float |
|
||||
| 浮点型 | double | 64 位(8 字节) | 4.9e-324 | 1.7976931348623157e308 | Double |
|
||||
| | void | | | | Void |
|
||||
|
||||
解释一下为什么会将 void 当为第 9 种基本数据类型:
|
||||
|
||||
在 Java 中,类型的分类有两种,一种是基本类型,一种是引用类型。两者的区别本质上在于:
|
||||
|
||||
- 基本类型 : 在 JVM 的栈中分配空间存 “值”。
|
||||
- 引用类型 : 在堆里面分配空间存 “值”。
|
||||
|
||||
Void 是不能 new 出来的(因为 Void 的源码中,将构造函数设置为 private 的,所以外部不能 new 对象),因此不能在堆里面分配空间存对应的值。那就是一开始在堆栈处分配好了空间。所以,将 Void 归成基本类型,也是有一定道理的。
|
||||
|
||||
> E 来表示单精度浮点数的指数符号。 如 1.254E+10 ,就是指 1.254 * 10^10 (单精度浮点数是指精确到 7 位的浮点数)
|
||||
> D 来表示双精度浮点数的指数符号。如 1.231346464D+10 (双精度浮点数精确到 15 位)
|
||||
>
|
||||
> 浮点数是小数点后边的位数吗?是的,浮点数是指小数点后的位数。
|
||||
>
|
||||
> 为什么 java 中的 double 最大值是 1.7976931348623157e308,用 e 表示不用 d
|
||||
> 在 Java 中,双精度浮点数(double)的最大值是 1.7976931348623157e308。这里使用的是科学计数法表示,即使用 "E" 来表示指数部分。在 Java 中,使用 "E" 来表示双精度浮点数的指数部分是一种约定俗成的写法,而不是强制规定。因此,虽然在科学计数法中通常使用 "E" 来表示指数,但在 Java 中也可以使用 "D" 来表示双精度浮点数,例如 1.7976931348623157D308。不过,通常情况下,Java 程序员更倾向于使用 "E" 来表示双精度浮点数的指数部分,以保持代码的一致性和可读性。
|
||||
|
||||
关于浮点类型的存储。
|
||||
|
||||
https://v.douyin.com/iFRAaA3v/
|
||||
|
||||
# 操作符
|
||||
|
||||
## 移位操作符
|
||||
|
||||
```
|
||||
int i = -1;
|
||||
System.out.println(Integer.toBinaryString(i));
|
||||
```
|
||||
|
||||
这段代码会输出 `11111111111111111111111111111111`。这是因为在 Java 中,`int` 类型是一个 32 位的二进制数。当我们使用 `-1` 时,它在内存中的表示形式是一个所有位都为 1 的二进制数,即 32 个 1。这是因为 Java 使用了一种叫做二进制补码的方式来表示负数。
|
||||
|
||||
二进制补码的计算方式是:首先取该数的绝对值的二进制形式,然后对这个二进制数取反(即 0 变 1,1 变 0),最后在结果的基础上加 1。例如,对于 -1,我们首先取其绝对值 1 的二进制形式(即 00000000000000000000000000000001),然后取反得到 11111111111111111111111111111110,最后加 1 得到 11111111111111111111111111111111。
|
||||
|
||||
`Integer.toBinaryString(i)` 这个方法会将整数 `i` 转换为二进制字符串形式,所以输出的结果就是 `11111111111111111111111111111111`。
|
||||
@@ -0,0 +1,181 @@
|
||||
# POM 文件
|
||||
|
||||
## `<parent>` 元素
|
||||
|
||||
- 用途:`<parent>` 元素用于继承父 POM 的配置。通过定义 `<parent>`,子项目可以继承父 POM 中的插件、依赖管理、属性、构建配置等。
|
||||
- 应用场景:适用于多模块项目(multi-module project),其中有一个通用的父项目提供共享的配置和依赖。
|
||||
- 继承特性:子项目会继承父项目的依赖管理(dependencyManagement)、插件管理(pluginManagement)等内容,但不会自动继承 `<dependencies>` 中的依赖。
|
||||
- 示例:
|
||||
|
||||
```xml
|
||||
<parent>
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>parent-project</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
```
|
||||
|
||||
## `<dependency>` 元素
|
||||
|
||||
- 用途:`<dependency>` 元素用于声明当前项目直接依赖的外部库或模块。Maven 会根据这些依赖项自动下载相关的 JAR 文件。
|
||||
- 应用场景:适用于项目需要使用外部库或其他模块时,直接声明依赖。
|
||||
- 继承特性:它不会继承,而是直接在当前项目中生效。
|
||||
- 示例:
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>5.3.10</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
# Settings 文件
|
||||
|
||||
## 阿里云公共仓库
|
||||
|
||||
```xml
|
||||
<mirror>
|
||||
<id>aliyunmaven</id>
|
||||
<mirrorOf>central</mirrorOf>
|
||||
<name>阿里云公共仓库</name>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
</mirror>
|
||||
```
|
||||
|
||||
# 私服搭建(Docker)
|
||||
|
||||
## 拉取并运行 Nexus 容器
|
||||
|
||||
1. **创建一个目录来存储 Nexus 数据**
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/nexus-data
|
||||
chmod -R 777 /opt/nexus-data
|
||||
ls -ld /opt/nexus-data
|
||||
```
|
||||
|
||||
1. **拉取并运行 Nexus Docker 镜像**
|
||||
|
||||
```bash
|
||||
docker run -d -p 8081:8081 --name nexus --restart unless-stopped -v /opt/nexus-data:/nexus-data sonatype/nexus3
|
||||
```
|
||||
|
||||
- `-d` 选项表示以后台模式运行容器。
|
||||
- `-p 8081:8081` 将容器的 8081 端口映射到主机的 8081 端口。
|
||||
- `--name nexus` 给容器命名为 `nexus`。
|
||||
- `-v ~/nexus-data:/nexus-data` 将主机上的目录 `~/nexus-data` 挂载到容器内的 `/nexus-data`,用于持久化数据。
|
||||
|
||||
## 访问 Nexus
|
||||
|
||||
Nexus 启动后,可以通过浏览器访问 `http://<your-ip>:8081`。初始页面可能需要一段时间加载,因为 Nexus 需要一些时间来初始化数据。
|
||||
|
||||
## 配置 Nexus
|
||||
|
||||
1. **登录**:默认管理员的用户名是 `admin`。初始密码可以在挂载数据卷的 `admin.password` 文件中找到:
|
||||
|
||||
```bash
|
||||
cat ~/nexus-data/admin.password
|
||||
```
|
||||
|
||||
2. **初始设置**:首次登录后,根据向导完成基本设置。
|
||||
|
||||
## 个人测试配置
|
||||
|
||||
```
|
||||
admin
|
||||
tianzhuo123.
|
||||
|
||||
tianzhuo
|
||||
tianzhuo123.
|
||||
```
|
||||
|
||||
# 问题记录
|
||||
|
||||
```log
|
||||
08:20:05 [] [] [] [] [main] ERROR org.springframework.boot.SpringApplication 870 reportFailure - Application run failed
|
||||
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NoClassDefFoundError: com/google/common/util/concurrent/internal/InternalFutureFailureAccess
|
||||
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at java.lang.Iterable.forEach(Iterable.java:75) ~[?:1.8.0_431]
|
||||
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:937) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.15.jar:2.5.15]
|
||||
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) ~[spring-boot-2.5.15.jar:2.5.15]
|
||||
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:453) ~[spring-boot-2.5.15.jar:2.5.15]
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:343) ~[spring-boot-2.5.15.jar:2.5.15]
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1370) ~[spring-boot-2.5.15.jar:2.5.15]
|
||||
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1359) ~[spring-boot-2.5.15.jar:2.5.15]
|
||||
at com.cmcc.cdn.platform.BootApplication.main(BootApplication.java:57) ~[classes/:?]
|
||||
Caused by: java.lang.NoClassDefFoundError: com/google/common/util/concurrent/internal/InternalFutureFailureAccess
|
||||
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[?:1.8.0_431]
|
||||
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.defineClass(URLClassLoader.java:473) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader$1.run(URLClassLoader.java:363) ~[?:1.8.0_431]
|
||||
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.findClass(URLClassLoader.java:362) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_431]
|
||||
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:359) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[?:1.8.0_431]
|
||||
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.defineClass(URLClassLoader.java:473) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader$1.run(URLClassLoader.java:363) ~[?:1.8.0_431]
|
||||
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.findClass(URLClassLoader.java:362) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_431]
|
||||
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:359) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.defineClass(ClassLoader.java:756) ~[?:1.8.0_431]
|
||||
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.defineClass(URLClassLoader.java:473) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.access$100(URLClassLoader.java:74) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader$1.run(URLClassLoader.java:369) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader$1.run(URLClassLoader.java:363) ~[?:1.8.0_431]
|
||||
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_431]
|
||||
at java.net.URLClassLoader.findClass(URLClassLoader.java:362) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_431]
|
||||
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:359) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_431]
|
||||
at com.google.common.cache.LocalCache$LoadingValueReference.<init>(LocalCache.java:3512) ~[guava-32.1.2-jre.jar:?]
|
||||
at com.google.common.cache.LocalCache$LoadingValueReference.<init>(LocalCache.java:3516) ~[guava-32.1.2-jre.jar:?]
|
||||
at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2169) ~[guava-32.1.2-jre.jar:?]
|
||||
at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2080) ~[guava-32.1.2-jre.jar:?]
|
||||
at com.google.common.cache.LocalCache.get(LocalCache.java:4012) ~[guava-32.1.2-jre.jar:?]
|
||||
at com.google.common.cache.LocalCache.getOrLoad(LocalCache.java:4035) ~[guava-32.1.2-jre.jar:?]
|
||||
at com.google.common.cache.LocalCache$LocalLoadingCache.get(LocalCache.java:5013) ~[guava-32.1.2-jre.jar:?]
|
||||
at springfox.documentation.schema.CachingModelProvider.modelFor(CachingModelProvider.java:59) ~[springfox-schema-2.9.2.jar:null]
|
||||
at springfox.documentation.spring.web.scanners.ApiModelReader.read(ApiModelReader.java:69) ~[springfox-spring-web-2.9.2.jar:null]
|
||||
at springfox.documentation.spring.web.scanners.ApiListingScanner.scan(ApiListingScanner.java:133) ~[springfox-spring-web-2.9.2.jar:null]
|
||||
at springfox.documentation.spring.web.scanners.ApiDocumentationScanner.scan(ApiDocumentationScanner.java:71) ~[springfox-spring-web-2.9.2.jar:null]
|
||||
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.scanDocumentation(DocumentationPluginsBootstrapper.java:101) ~[springfox-spring-web-2.9.2.jar:null]
|
||||
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:167) ~[springfox-spring-web-2.9.2.jar:null]
|
||||
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.28.jar:5.3.28]
|
||||
... 14 more
|
||||
Caused by: java.lang.ClassNotFoundException: com.google.common.util.concurrent.internal.InternalFutureFailureAccess
|
||||
at java.net.URLClassLoader.findClass(URLClassLoader.java:387) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:418) ~[?:1.8.0_431]
|
||||
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:359) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.loadClass(ClassLoader.java:351) ~[?:1.8.0_431]
|
||||
at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_431]
|
||||
```
|
||||
|
||||
```
|
||||
因为拉下来的 POM 存在问题。
|
||||
```
|
||||
|
||||

|
||||
|
||||
可以把中央镜像仓库放在前边。优先中央镜像。
|
||||
@@ -0,0 +1,80 @@
|
||||
# Demo
|
||||
|
||||
```java
|
||||
package org.example.test;
|
||||
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers;
|
||||
|
||||
public class RxJavaDemo {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 创建一个简单的Observable,发出三个事件
|
||||
Observable<String> simpleObservable = Observable.create(emitter -> {
|
||||
// 发送三个事件
|
||||
emitter.onNext("RxJava");
|
||||
emitter.onNext("is");
|
||||
emitter.onNext("Powerful!");
|
||||
emitter.onComplete(); // 发送完成事件
|
||||
});
|
||||
|
||||
// 订阅Observable并定义观察者行为
|
||||
simpleObservable
|
||||
// 将发射项转换为大写
|
||||
.map(String::toUpperCase)
|
||||
// 在观察者的操作中添加延时模拟异步操作
|
||||
.delay(1, java.util.concurrent.TimeUnit.SECONDS)
|
||||
// 运行在io调度器上,用于io密集型操作,比如文件操作,网络调用等。
|
||||
.subscribeOn(Schedulers.io())
|
||||
// 观察结果运行在计算调度器上,用于CPU密集型计算任务
|
||||
.observeOn(Schedulers.computation())
|
||||
.subscribe(
|
||||
// onNext方法处理
|
||||
item -> System.out.println("Observer received: " + item + " on thread " + Thread.currentThread().getName()),
|
||||
// onError方法处理
|
||||
Throwable::printStackTrace,
|
||||
// onComplete方法处理
|
||||
() -> System.out.println("Observer got Completed event")
|
||||
);
|
||||
|
||||
// 由于Observable是异步的,我们需要让主线程等待,否则main方法可能在Observable完成之前结束
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
private static void sleep(long millis) {
|
||||
try {
|
||||
Thread.sleep(millis);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava3</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
# 好处
|
||||
|
||||
RxJava 的好处在于其提供了一种强大且灵活的方式来处理异步和基于事件的程序设计。它是响应式编程的一个 Java 实现,响应式编程是一种面向数据流和变化传播的编程范式。
|
||||
|
||||
以下是使用 RxJava 的一些优势:
|
||||
|
||||
1. **函数式编程风格**:RxJava 鼓励使用不可变值和函数式操作符,这可以减少错误并促进清晰的编程模式。
|
||||
2. **链式调用**:RxJava 操作符可以被链式调用,这使得创建复杂的数据流变得直观且可读性强。
|
||||
3. **组合异步操作**:RxJava 强大的组合操作符支持你轻松组合多个异步操作,并以声明式的方式处理它们的输出。
|
||||
4. **错误处理**:RxJava 提供了全面的错误处理操作符,可以让你控制错误的传播和处理策略。
|
||||
5. **背压支持**:RxJava 支持背压,即处理在异步流中生产者生成事件速度快于消费者消费的情况。
|
||||
6. **线程控制**:RxJava 通过 `Schedulers` 类,允许你明确地控制工作在哪个线程运行。
|
||||
7. **松耦合**:RxJava 可以帮助创建松耦合的系统,系统的不同部分可以易于独立更改和重构。
|
||||
|
||||
与传统的异步处理(例如使用 Java 中的 `Future` 和 `Callable`)相比,RxJava 更加灵活和强大。在传统的异步模式中,处理多个异步操作并且需要它们协同工作时可能会非常复杂。而 RxJava 通过提供丰富的操作符集,使得合并、过滤、转换、延迟异步事件流成为可能。
|
||||
|
||||
例如,如果你想合并来自不同数据源的数据,使用传统的异步模式可能需要编写复杂的同步代码。而在 RxJava 中,你只需使用 `zip` 或 `merge` 等操作符即可轻松实现。
|
||||
|
||||
总的来说,RxJava 提供了一个强大的抽象层,使得开发者能够以声明式的方式处理异步数据流,而不必担心底层的线程管理和复杂的状态同步问题。
|
||||
@@ -0,0 +1,184 @@
|
||||
# 差别
|
||||
|
||||
Spring WebFlux 和传统的 Spring MVC (使用@Controller 和@RequestMapping 等注解定义的控制器)之间主要的区别在于它们对于处理请求的方式不同,这主要体现在响应式编程范式和阻塞式编程范式的差异上。下面我将通过几个方面来进行通俗说明。
|
||||
|
||||
## 编程模型
|
||||
|
||||
**Spring MVC**:
|
||||
- **阻塞式 I/O 操作**:每个 HTTP 请求通常在一个单独的线程中被处理,如果请求处理涉及到长时间的 I/O 操作(如数据库查询),那么该线程会在操作完成之前处于阻塞状态。
|
||||
- **注解驱动**:使用 `@Controller` 和 `@RequestMapping` 之类的注解来定义路由和处理方法,这些注解通常与同步操作一起使用。
|
||||
|
||||
**Spring WebFlux**:
|
||||
- **非阻塞式 I/O 操作**:使用响应式编程模型,即使操作是长时间运行的,也不会阻塞线程。而是在操作可以继续进行时,以回调的方式恢复执行。
|
||||
- **函数式端点定义**:虽然 WebFlux 也支持与 Spring MVC 相同的注解方式,但它引入了一个新的函数式编程模型,可以使用路由器函数(RouterFunction)和处理器函数(HandlerFunction)来声明路由和处理逻辑。
|
||||
|
||||
## 底层运行库
|
||||
|
||||
- **Spring MVC**:通常运行在阻塞式的 Web 服务器上,如 Tomcat。
|
||||
- **Spring WebFlux**:可以运行在支持非阻塞运行环境的服务器上,如 Netty、Undertow。
|
||||
|
||||
## 性能和资源利用
|
||||
|
||||
- **Spring MVC**:在高负载时,每个请求占用一个线程,可能会导致线程资源耗尽。
|
||||
- **Spring WebFlux**:响应式编程使得可以用较少的线程来处理大量并发请求,从而提高了线程的利用率。
|
||||
|
||||
## 应用场景
|
||||
|
||||
- **Spring MVC**:适用于传统的、请求 - 响应式的 Web 应用程序,特别是当你有很多的同步操作时。
|
||||
- **Spring WebFlux**:适用于需要处理大量并发连接,以及要求低延迟 & 高吞吐量的实时性应用,例如实时数据处理、在线游戏后端等。
|
||||
|
||||
## 影响
|
||||
|
||||
- **开发风格**:WebFlux 的函数式风格可能需要开发者学习新的编程范式,而 Spring MVC 的注解风格更符合大多数 Java 开发者的习惯。
|
||||
- **可维护性**:WebFlux 的函数式风格可以带来更好的可测试性和可维护性,但这取决于开发者对响应式编程的熟悉程度。
|
||||
- **响应性**:WebFlux 更适合做到背压和流式数据处理,可以更好地支持响应式系统的需求。
|
||||
|
||||
总结来说,Spring WebFlux 和 Spring MVC 提供了不同的编程模型来适应不同的应用场景。WebFlux 响应式编程模型适合处理大量并发请求和实时交互,而传统的 Spring MVC 更适合标准的、阻塞式的 Web 应用开发。选择使用哪个,取决于你的具体需求和开发团队的技术偏好。
|
||||
|
||||
# 非阻塞式 I/O 操作
|
||||
|
||||
非阻塞式 I/O 操作:使用响应式编程模型,即使操作是长时间运行的,也不会阻塞线程。而是在操作可以继续进行时,以回调的方式恢复执行。
|
||||
|
||||
这个特点。
|
||||
|
||||
下面是一个简单的 Spring WebFlux 的示例,展示了如何创建一个非阻塞的 RESTful 接口,该接口模拟了一个长时间运行的操作,比如查询数据库,但在这个示例中我们将使用 `Mono.delay` 来模拟这个耗时操作。
|
||||
|
||||
请注意,此代码需要在 Spring WebFlux 项目中运行,且 Spring Boot 版本应支持 WebFlux。
|
||||
|
||||
首先,我们需要一个模型,这里我们使用一个非常简单的 `Greeting` 类:
|
||||
|
||||
```java
|
||||
public class Greeting {
|
||||
private String message;
|
||||
|
||||
public Greeting(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
// getters and setters
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
接着,我们创建一个处理器(handler)来处理 HTTP 请求:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class GreetingHandler {
|
||||
|
||||
public Mono<ServerResponse> hello(ServerRequest request) {
|
||||
// Mono.delay(Duration.ofSeconds(3)) 模拟一个长时间运行的操作
|
||||
Mono<Greeting> greetingMono = Mono.delay(Duration.ofSeconds(3))
|
||||
.map(l -> new Greeting("Hello, Spring WebFlux!"));
|
||||
|
||||
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.body(greetingMono, Greeting.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在,我们需要定义路由来关联 URL 路径和处理器:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public class GreetingRouter {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> route(GreetingHandler greetingHandler) {
|
||||
return RouterFunctions
|
||||
.route(RequestPredicates.GET("/hello"), greetingHandler::hello);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
最后,这是 `SpringWebFluxDemoApplication` 的主类,用以启动 Spring Boot 应用:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
public class SpringWebFluxDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringWebFluxDemoApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
要测试非阻塞行为,请在浏览器或使用 curl 访问 `http://localhost:8080/hello`。
|
||||
|
||||
在我们的 `GreetingHandler` 中,`Mono.delay(Duration.ofSeconds(3))` 模拟了一个长时间运行的 I/O 操作,这个操作在 3 秒后完成。这里的关键是,`Mono.delay` 创建了一个非阻塞的延迟操作,这意味着在这 3 秒内,执行这个操作的线程不会被阻塞,它可以去做其他的事情。
|
||||
|
||||
要测试非阻塞行为,请在浏览器或使用 curl 访问 `http://localhost:8080/hello`。即便我们在 `GreetingHandler` 中模拟了一个 3 秒的延迟,你会发现服务仍然可以接收和处理其他的请求。如果你并行地从多个终端或浏览器窗口发送请求,你会看到它们几乎是同时响应的,而不是一个接一个地阻塞等待。
|
||||
|
||||
为了完整起见,以下是使用 curl 命令行测试的方式:
|
||||
|
||||
```shell
|
||||
curl http://localhost:8080/hello
|
||||
```
|
||||
|
||||
如果你从多个终端窗口同时发出多个请求,你应该会发现响应时间几乎相同,这说明服务器没有因为模拟的延迟操作而阻塞其他请求。
|
||||
|
||||
这个简单的 demo 展示了 WebFlux 如何处理长时间运行的操作。在实际的生产环境中,类似的非阻塞操作可能包括数据库查询、远程服务调用等,这些操作可以利用响应式编程模型完成,而不会导致线程阻塞,从而提高了应用程序的性能和伸缩性。
|
||||
|
||||
# 真实场景
|
||||
|
||||
那真实的业务场景一般是怎么设计的?也是用 Mono.delay 吗?可以给我一个简答的真实场景的示例方案吗?比如一个数据查询接口,当收到请求后需要去大数据查询数据。查询大数据是比较耗时的。
|
||||
|
||||
当然可以。在许多微服务架构或分布式系统中,经常需要通过 HTTP 请求从其他服务或平台获取数据。如果你想在响应式编程模型中保持非阻塞式调用,你可以使用 Spring WebFlux 的 `WebClient`,这是一个响应式的 HTTP 客户端,用于执行 HTTP 请求并处理响应。
|
||||
|
||||
下面是如何使用 Spring WebFlux 的 `WebClient` 从另一个服务获取数据的示例:
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class BigDataService {
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
public BigDataService(WebClient.Builder webClientBuilder) {
|
||||
this.webClient = webClientBuilder.baseUrl("http://bigdata-service").build();
|
||||
}
|
||||
|
||||
public Mono<BigDataResponse> queryBigData(String queryParam) {
|
||||
return webClient.get()
|
||||
.uri(uriBuilder -> uriBuilder.path("/query")
|
||||
.queryParam("param", queryParam)
|
||||
.build())
|
||||
.retrieve()
|
||||
.bodyToMono(BigDataResponse.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这个服务中,我们创建了一个 `WebClient` 实例,它被配置为指向大数据平台的服务。`queryBigData` 方法通过 HTTP GET 请求发送查询参数,非阻塞地从大数据平台请求数据。返回的对象 `Mono<BigDataResponse>` 是一个响应式的单值流,它将在异步操作完成时发出一个 `BigDataResponse` 对象。
|
||||
|
||||
然后你可以在控制层调用这个服务:
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/data")
|
||||
public class DataController {
|
||||
|
||||
private final BigDataService bigDataService;
|
||||
|
||||
@Autowired
|
||||
public DataController(BigDataService bigDataService) {
|
||||
this.bigDataService = bigDataService;
|
||||
}
|
||||
|
||||
@GetMapping("/query")
|
||||
public Mono<ResponseEntity<BigDataResponse>> queryData(@RequestParam String param) {
|
||||
return bigDataService.queryBigData(param)
|
||||
.map(response -> ResponseEntity.ok(response))
|
||||
.defaultIfEmpty(ResponseEntity.notFound().build());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里,`DataController` 调用 `BigDataService` 的 `queryBigData` 方法,并将返回的 `Mono` 直接作为响应返回。Spring 框架会处理这个 `Mono`,当大数据平台返回结果时,将其序列化并发送到客户端。
|
||||
|
||||
使用 `WebClient`,Spring WebFlux 保证了整个 HTTP 调用过程是非阻塞的,即使是在等待远程大数据平台的响应时,也不会占用线程资源,这使得应用能够高效处理其他并发请求。
|
||||
@@ -0,0 +1,57 @@
|
||||
# 概念
|
||||
|
||||
令牌桶是一种用于分组交换和电信网络的算法。它可用于检查数据包形式的数据传输是否符合定义的带宽和突发性限制(流量不均匀或变化的度量)
|
||||
|
||||
# maven 依赖
|
||||
|
||||
```
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>30.1-jre</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
# 代码实现
|
||||
|
||||
```java
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import com.google.common.util.concurrent.RateLimiter;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100));
|
||||
|
||||
RateLimiter limiter = RateLimiter.create(1);
|
||||
for (int i = 1; i < 50; i++) {
|
||||
Double acquire = limiter.acquire(10);
|
||||
executorService.submit(new PrintTask(String.format("get token,token:%s consumer:%s, taskId:%s", acquire, acquire, i)));
|
||||
}
|
||||
}
|
||||
|
||||
static class PrintTask implements Runnable {
|
||||
String out;
|
||||
|
||||
public PrintTask(String out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
System.out.println(sdf.format(new Date()) + " -> " + Thread.currentThread().getName() + out);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# 总结
|
||||
|
||||
其中核心 `RateLimiter limiter = RateLimiter.create(1);` 和 `Double acquire = limiter.acquire(10);` 相当于是实现 qps 的标准比值。
|
||||
|
||||
前者为生成令牌的个数每秒,后者为获取令牌的个数。
|
||||
@@ -0,0 +1,32 @@
|
||||
```java
|
||||
// onjava/TimedAbort.java
|
||||
// (c)2021 MindView LLC: see Copyright.txt
|
||||
// We make no guarantees that this code is fit for any purpose.
|
||||
// Visit http://OnJava8.com for more book information.
|
||||
// Terminate a program after t seconds
|
||||
package onjava;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class TimedAbort {
|
||||
private volatile boolean restart = true;
|
||||
public TimedAbort(double t, String msg) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
while(restart) {
|
||||
restart = false;
|
||||
TimeUnit.MILLISECONDS
|
||||
.sleep((int)(1000 * t));
|
||||
}
|
||||
} catch(InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
System.out.println(msg);
|
||||
System.exit(0);
|
||||
});
|
||||
}
|
||||
public TimedAbort(double t) {
|
||||
this(t, "TimedAbort " + t);
|
||||
}
|
||||
public void restart() { restart = true; }
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
## Function parser
|
||||
|
||||
这里可以把 parser 当作是功能性接口,进行数据处理。
|
||||
|
||||

|
||||
|
||||
## QUARTZ
|
||||
|
||||

|
||||
|
||||
# @Temporal
|
||||
|
||||
如果类中有 Date 类型的属性,数据库中存储的是“yyyy-MM-dd hh:MM:ss”但是查询的时候要获得年月日,在该属性上标注
|
||||
|
||||
@Temporal(TemporalType.DATE)
|
||||
|
||||
TemporalType 可以控制获得的类型。
|
||||
@@ -0,0 +1,9 @@
|
||||
| 中文名称 | 英文全称 | 常用缩写 (常用程度从左到右) |
|
||||
| :------ | :----------------------- | :-------------------------- |
|
||||
| 开发环境 | Development | `dev` (最常用), `develop` |
|
||||
| 联调环境 | Integration Test | `sit` (最常用), `int` |
|
||||
| 测试环境 | Testing / QA | `test`, `qa` |
|
||||
| 用户验收测试 | User Acceptance Test | `uat`, `pre` (有时也指预发) |
|
||||
| 预发/灰度环境 | Staging / Pre-production | `stg` (最常用), `stage`, `pre` |
|
||||
| 生产环境 | Production | `prod` (最常用), `prd`, `live` |
|
||||
| 本地环境 | Local | `local` |
|
||||
Reference in New Issue
Block a user