diff --git a/public/api/i/2025/08/08/f82hdp-1.webp b/public/api/i/2025/08/08/f82hdp-1.webp new file mode 100755 index 0000000..25b6c56 Binary files /dev/null and b/public/api/i/2025/08/08/f82hdp-1.webp differ diff --git a/public/api/i/2025/08/08/f8ed5e-1.webp b/public/api/i/2025/08/08/f8ed5e-1.webp new file mode 100755 index 0000000..7c8d412 Binary files /dev/null and b/public/api/i/2025/08/08/f8ed5e-1.webp differ diff --git a/public/api/i/2025/08/08/fkc5xn-1.webp b/public/api/i/2025/08/08/fkc5xn-1.webp new file mode 100755 index 0000000..15dddb4 Binary files /dev/null and b/public/api/i/2025/08/08/fkc5xn-1.webp differ diff --git a/public/api/i/2025/08/08/gzh7kd-1.webp b/public/api/i/2025/08/08/gzh7kd-1.webp new file mode 100755 index 0000000..26a2f64 Binary files /dev/null and b/public/api/i/2025/08/08/gzh7kd-1.webp differ diff --git a/src/content/posts/Java/Spring/Spring中的BeanFactory与FactoryBean.md b/src/content/posts/Java/Spring/Spring中的BeanFactory与FactoryBean.md new file mode 100644 index 0000000..15ace71 --- /dev/null +++ b/src/content/posts/Java/Spring/Spring中的BeanFactory与FactoryBean.md @@ -0,0 +1,265 @@ +--- +title: Spring中的BeanFactory与FactoryBean +published: 2025-08-08 +description: 深入理解Spring容器的核心接口BeanFactory与特殊工厂Bean——FactoryBean的区别、使用场景与最佳实践 +image: '' +tags: [Java, Spring, IoC, DI, BeanFactory, FactoryBean] +category: 'JAVA > Spring' +draft: false +lang: zh-CN +--- + +# BeanFactory +BeanFactory是一个工厂接口,是一个负责生产和管理bean的一个工厂。BeanFactory是工厂的顶层接口,是IOC容器的核心接口,BeanFactory定义了管理Bean的通用方法,如getBean和containsBean等,它的职责包括: +- Bean实例化: 根据XML注解等创建Bean对象。 +- 依赖注入: 自动将Bean所需的依赖注入进去。 +- 生命周期管理: 管理Bean的初始化,销毁等生命周期方法。 +- 延迟加载: 默认采用懒加载策略,只有在调用getBean()时才创建Bean实例。 +- Bean获取: 提供getBean()方法来获取Bean实例。 + +![](https://blog.meowrain.cn/api/i/2025/08/08/fkc5xn-1.webp) + +BeanFactory只是一个接口,不是IOC容器的具体实现,所以Spring容器给出了很多种实现,如XmlBeanFactory、AnnotationConfigApplicationContext,ApplicationContext等。 + +## BeanFactory 的常见实现类 + +Spring 提供了多种 BeanFactory 的实现,每种实现都有其特定的使用场景: + +### DefaultListableBeanFactory +最常用的完整实现,支持完整的Bean生命周期管理: + +```java +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.support.RootBeanDefinition; + +public class BeanFactoryExample { + public static void main(String[] args) { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + + // 手动注册Bean定义 + RootBeanDefinition beanDefinition = new RootBeanDefinition(MyService.class); + factory.registerBeanDefinition("myService", beanDefinition); + + // 获取Bean + MyService service = factory.getBean("myService", MyService.class); + service.doSomething(); + } +} + +class MyService { + public void doSomething() { + System.out.println("Service is working!"); + } +} +``` + +### XmlBeanFactory(已废弃) +基于XML配置的BeanFactory实现,Spring 5.x后已废弃,推荐使用ApplicationContext: + +```java +// 传统用法(不推荐) +// XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml")); + +// 现代替代方案 +import org.springframework.context.support.ClassPathXmlApplicationContext; + +ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); +MyService service = context.getBean("myService", MyService.class); +``` + +### StaticListableBeanFactory +静态Bean工厂,适用于Bean集合固定的场景: + +```java +import org.springframework.beans.factory.support.StaticListableBeanFactory; + +StaticListableBeanFactory factory = new StaticListableBeanFactory(); +factory.addBean("myService", new MyService()); +factory.addBean("anotherService", new AnotherService()); + +MyService service = factory.getBean("myService", MyService.class); +``` + +### ApplicationContext实现类 +作为BeanFactory的高级实现,提供更多企业级特性: + +```java +// 注解配置 +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +AnnotationConfigApplicationContext context = + new AnnotationConfigApplicationContext(AppConfig.class); + +// XML配置 +import org.springframework.context.support.ClassPathXmlApplicationContext; + +ClassPathXmlApplicationContext xmlContext = + new ClassPathXmlApplicationContext("applicationContext.xml"); + +// Web环境 +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; + +AnnotationConfigWebApplicationContext webContext = + new AnnotationConfigWebApplicationContext(); +webContext.register(WebConfig.class); +``` + +### 实际应用示例 +结合Bean定义构建器的完整示例: + +```java +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; + +public class CustomBeanFactoryDemo { + public static void main(String[] args) { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + + // 使用BeanDefinitionBuilder构建复杂Bean + BeanDefinitionBuilder builder = BeanDefinitionBuilder + .rootBeanDefinition(DatabaseService.class) + .addPropertyValue("url", "jdbc:mysql://localhost:3306/mydb") + .addPropertyValue("username", "root") + .setScope("singleton") + .setLazyInit(true); + + factory.registerBeanDefinition("dbService", builder.getBeanDefinition()); + + // 懒加载验证 + System.out.println("Bean定义已注册,但未实例化"); + + DatabaseService dbService = factory.getBean("dbService", DatabaseService.class); + System.out.println("现在Bean被实例化了"); + } +} + +class DatabaseService { + private String url; + private String username; + + // getters and setters + public void setUrl(String url) { this.url = url; } + public void setUsername(String username) { this.username = username; } + + public void connect() { + System.out.println("连接到: " + url + " 用户: " + username); + } +} +``` + + +## BeanFactory 与 ApplicationContext 的区别 +- 预实例化策略 + - BeanFactory:单例默认懒加载。 + - ApplicationContext:默认预实例化单例(提高启动后首次访问的吞吐)。 +- 扩展能力 + - ApplicationContext 额外提供国际化、事件发布、AOP自动代理、资源模式解析等企业特性。 +- 适用场景 + - BeanFactory:资源受限、极致冷启动、强控制懒加载/条件加载。 + - ApplicationContext:大多数应用优先选择。 +- 调优提示 + - 需要懒加载时,可结合ApplicationContext + @Lazy 或者使用ObjectProvider/Provider按需获取。 + +# FactoryBean +在Spring中,所有的Bean都是由BeanFactory管理的(IOC容器), +这个FactoryBean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。 + + +## FactoryBean 的作用 + +![](https://blog.meowrain.cn/api/i/2025/08/08/gzh7kd-1.webp) + +- 将“复杂对象的创建逻辑”封装到工厂Bean中,对外暴露的是“产品对象”而不是工厂本身。 +- 常用于:动态代理(AOP/远程代理)、框架桥接(如MyBatis的SqlSessionFactoryBean)、复杂构建(连接池、客户端SDK)。 + +## 核心接口方法 +- getObject(): 返回实际产品对象(对外暴露的Bean)。 +- getObjectType(): 返回产品类型,便于类型匹配与自动装配。 +- isSingleton(): 决定产品是否为单例(影响缓存与生命周期)。 + +## 获取“产品”还是“工厂本身” +- 普通名:context.getBean("beanName") 获取的是产品对象(getObject返回值)。 +- 带&前缀:context.getBean("&beanName") 获取的是FactoryBean自身。 +- 命名规则:注册名为 x 的 FactoryBean,会对外暴露“产品”名为 x,“工厂自身”为 &x。 + +## 最小可运行示例 +```java +// 一个业务产品 +public class ApiClient { + private final String endpoint; + public ApiClient(String endpoint) { this.endpoint = endpoint; } + public String call(String path) { return "GET " + endpoint + path; } +} + +// FactoryBean 实现 +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +public class ApiClientFactoryBean implements FactoryBean, InitializingBean { + private String endpoint; + public void setEndpoint(String endpoint) { this.endpoint = endpoint; } + + @Override + public ApiClient getObject() { + // 可在此放入复杂构建/代理/缓存等逻辑 + return new ApiClient(endpoint); + } + + @Override + public Class getObjectType() { return ApiClient.class; } + + @Override + public boolean isSingleton() { return true; } + + @Override + public void afterPropertiesSet() { + Assert.hasText(endpoint, "endpoint must not be empty"); + } +} + +// Java 配置注册 FactoryBean +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + @Bean(name = "apiClient") + public ApiClientFactoryBean apiClientFactoryBean() { + ApiClientFactoryBean fb = new ApiClientFactoryBean(); + fb.setEndpoint("https://api.example.com"); + return fb; + } +} + +// 取产品与取工厂本身 +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class Demo { + public static void main(String[] args) { + ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); + ApiClient client = ctx.getBean("apiClient", ApiClient.class); // 产品 + ApiClientFactoryBean fb = ctx.getBean("&apiClient", ApiClientFactoryBean.class); // 工厂本身 + System.out.println(client.call("/ping")); + } +} +``` + +## 常见坑与最佳实践 +- getObjectType 切勿返回 null:尽量返回接口/具体类型,便于按类型注入与AOT分析。 +- isSingleton 与产品生命周期:单例将被容器缓存;非单例每次getBean都会重新创建产品。 +- 懒加载与预实例化:在ApplicationContext中,如希望延迟创建可使用@Lazy或将FactoryBean产品设为非单例。 +- 自动装配歧义:按类型注入时,注入到的是产品类型而非FactoryBean;需要注入工厂本身时使用@Qualifier("&name")或@Resource(name="&name")。 +- 原型产品与循环依赖:原型产品不参与循环依赖的三级缓存提前暴露,避免在原型链路中引入循环依赖。 +- 命名规范:确保文档/注释标明“&”语义,避免团队误用。 + +## 什么时候用哪一个? +- 仅需容器功能:优先ApplicationContext(功能更全,默认预实例化)。 +- 需要懒加载到极致:考虑BeanFactory或在ApplicationContext中对关键Bean标注@Lazy。 +- 对象构建复杂/需代理/外部SDK桥接:使用FactoryBean封装构建细节,对外仅暴露产品。 + +## 小结 +- BeanFactory 是IoC容器的最小抽象;ApplicationContext在其上增强了企业级特性。 +- FactoryBean 是“创建Bean的Bean”,对外暴露产品;使用“&name”获取工厂本身。 +- 合理利用FactoryBean可显著简化复杂对象创建,并保持应用装配的清晰与解耦。 \ No newline at end of file diff --git a/src/content/posts/Java/Spring/Spring配置相关的注解.md b/src/content/posts/Java/Spring/Spring配置相关的注解.md new file mode 100644 index 0000000..5d4e10e --- /dev/null +++ b/src/content/posts/Java/Spring/Spring配置相关的注解.md @@ -0,0 +1,291 @@ +--- +title: Spring配置相关的注解 +published: 2025-08-08 +description: '' +image: '' +tags: ["Spring","Java"] +category: 'Java > Spring' +draft: false +lang: '' +--- + + +当然!这是一个非常重要且实用的主题。在 Spring 和 Spring Boot 中,与属性(Property)相关的注解是实现“配置与代码分离”这一核心原则的关键。 + +我将为你全面、系统地讲解这些注解,从最基础到最常用,再到高级用法,并配上清晰的示例。 + +我们将主要围绕以下几个核心注解展开: + +1. **`@Value`**: 最基础的,用于注入单个属性值。 +2. **`@PropertySource`**: 用于加载指定的属性文件。 +3. **`@ConfigurationProperties`**: 最强大、最推荐的,用于类型安全地将一组属性绑定到Java对象上。 +4. **`@EnableConfigurationProperties`**: 与 `@ConfigurationProperties` 配合使用,用于激活属性绑定。 +5. **`@TestPropertySource`**: 在测试环境中加载或覆盖属性。 + +--- + +### 注解族谱概览 + +为了方便理解,我们可以把它们分为三类: + +* **值注入 (Value Injection)**: `@Value` +* **配置源 (Configuration Source)**: `@PropertySource`, `@TestPropertySource` +* **批量绑定 (Bulk Binding)**: `@ConfigurationProperties`, `@EnableConfigurationProperties` + +--- + +### 1. `@Value`:简单直接的“单兵作战” + +这是注入属性最基本的方式。 + +* **作用**: 将 Spring 环境(Environment)中的单个属性值注入到类的字段或方法参数中。 +* **语法**: 使用 SpEL (Spring Expression Language) 表达式 `"${property.key}"`。 +* **优点**: 简单、直接,适合注入少量、分散的配置。 +* **缺点**: + * 当属性很多时,代码会显得分散和混乱。 + * 类型安全性较弱(都是字符串,需要Spring转换)。 + * 重构时(如修改前缀)非常痛苦。 + +**示例:** + +**`src/main/resources/application.properties`** +```properties +app.name=My Awesome App +app.version=2.1.5 +app.author.name=Alex +# 如果某个属性可能不存在,可以提供默认值 +# mail.default.sender=default@example.com +``` + +**Java 类** +```java +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class AppInfoService { + + // 注入 app.name 属性 + @Value("${app.name}") + private String appName; + + // 注入 app.version 属性 + @Value("${app.version}") + private String appVersion; + + // 注入一个不存在的属性,但提供了默认值 "unknown" + @Value("${app.description:unknown description}") + private String appDescription; + + // 也可以注入其他 Bean 的属性(使用 SpEL) + @Value("#{someOtherBean.someProperty}") + private String otherProperty; + + public void printAppInfo() { + System.out.println("App Name: " + appName); + System.out.println("App Version: " + appVersion); + System.out.println("App Description: " + appDescription); + } +} +``` + +--- + +### 2. `@PropertySource`:指定“情报来源” + +默认情况下,Spring Boot 会自动加载 `application.properties` 或 `application.yml`。如果你想加载其他配置文件,就需要用到 `@PropertySource`。 + +* **作用**: 将指定的属性文件加载到 Spring 的 `Environment` 中。 +* **使用场景**: + * 模块化配置,将不同功能的配置放在不同文件里(如 `mail.properties`, `db.properties`)。 + * 加载 classpath 之外的文件系统中的配置。 +* **注意**: 它只负责**加载**,不负责注入。加载后,你可以用 `@Value` 或 `@ConfigurationProperties` 来使用这些属性。 + +**示例:** + +**`src/main/resources/mail.properties`** +```properties +mail.host=smtp.gmail.com +mail.port=587 +``` + +**Java 配置类** +```java +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.PropertySource; +import org.springframework.beans.factory.annotation.Value; + +@Configuration +// 加载 classpath 下的 mail.properties 文件 +@PropertySource("classpath:mail.properties") +public class MailConfig { + + @Value("${mail.host}") + private String host; + + @Value("${mail.port}") + private int port; + + // ... +} +``` + +--- + +### 3. `@ConfigurationProperties` + +这是 Spring Boot **最推荐**的属性管理方式。它将一组相关的属性映射到一个类型安全的 Java 对象(POJO)上。 + +* **作用**: 将具有相同前缀的属性批量绑定到一个 POJO 的字段上。 +* **优点**: + * **类型安全**: 直接映射到 `int`, `List`, `Duration` 等各种类型。 + * **结构清晰**: 将相关配置聚合在一个类中,非常易于管理和维护。 + * **强大的绑定**: 支持复杂的对象图,比如嵌套对象、列表、Map等。 + * **IDE 友好**: 主流 IDE(如 IntelliJ IDEA)支持对 `application.properties` 中这类属性的自动补全和导航(需要添加 `spring-boot-configuration-processor` 依赖)。 + +**示例:** + +**`application.properties`** +```properties +app.server.name=prod-server +app.server.ip-address=192.168.1.100 +app.server.timeout=30s # Spring Boot 2.x 支持时间单位 +app.server.admins[0].name=admin1 +app.server.admins[0].email=admin1@corp.com +app.server.admins[1].name=admin2 +app.server.admins[1].email=admin2@corp.com +``` + +**Java 属性类 (POJO)** + +```java +import org.springframework.boot.context.properties.ConfigurationProperties; +import java.time.Duration; +import java.util.List; + +// 告诉 Spring 这个类要绑定前缀为 "app.server" 的属性 +@ConfigurationProperties(prefix = "app.server") +public class ServerProperties { + + private String name; + private String ipAddress; + private Duration timeout; // 自动将 "30s" 转换为 Duration 对象 + private List admins; + + // 嵌套类 + public static class Admin { + private String name; + private String email; + // Getters and Setters for Admin + } + + // ⭐ 重要: 必须为所有字段提供 public Getters and Setters + // Spring 通过它们来注入值 + // ... Getters and Setters for ServerProperties ... +} +``` + +--- + +### 4. `@EnableConfigurationProperties` + +`@ConfigurationProperties` 只是一个声明,它本身不会让这个 POJO 成为一个 Spring Bean。你需要一种方式来“激活”它。`@EnableConfigurationProperties` 就是这个开关。 + +* **作用**: + 1. 告诉 Spring 去处理被 `@ConfigurationProperties` 注解的类。 + 2. 将被注解的类(如 `ServerProperties`)注册到 Spring 容器中,让它成为一个 Bean。这样你就可以在其他地方 `@Autowired` 注入它了。 +* **通常放在哪**: 主启动类或任何 `@Configuration` 类上。 + +**示例:** + +```java +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +// 激活对 ServerProperties 类的绑定,并将其注册为 Bean +@EnableConfigurationProperties(ServerProperties.class) +public class MyApplication { + + public static void main(String[] args) { + SpringApplication.run(MyApplication.class, args); + } +} + +// 现在可以在任何其他组件中注入它 +@Service +public class MyService { + private final ServerProperties serverProps; + + @Autowired + public MyService(ServerProperties serverProps) { + this.serverProps = serverProps; + System.out.println("Server Name: " + serverProps.getName()); + } +} +``` + +> **快捷方式**: 如果你在 `ServerProperties` 类上同时加上 `@Component` 和 `@ConfigurationProperties`,就可以省略 `@EnableConfigurationProperties`。但显式使用 `@EnableConfigurationProperties` 通常被认为是更清晰的做法,因为它明确表达了这是一个配置类。 + +--- + +### 5. `@TestPropertySource`:为测试“定制情报” + +在进行单元测试或集成测试时,我们经常需要使用一套不同于生产环境的配置(比如连接到内存数据库 H2)。 + +* **作用**: 在测试上下文中加载属性,它可以覆盖`application.properties`中的属性或添加新属性。 +* **常用属性**: + * `locations`: 指定要加载的属性文件路径。 + * `properties`: 以 `key=value` 形式直接定义内联属性。 + +**示例:** + +```java +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.core.env.Environment; +import org.springframework.test.context.TestPropertySource; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +// 1. 加载 test.properties 文件 +// 2. 直接定义一个内联属性,它会覆盖 test.properties 或 application.properties 中的同名属性 +@TestPropertySource( + locations = "classpath:test.properties", + properties = "app.version=test-1.0" +) +public class AppInfoServiceTest { + + @Autowired + private Environment env; + + @Test + void testPropertiesAreLoaded() { + // 来自 test.properties + assertThat(env.getProperty("app.name")).isEqualTo("Test App"); + + // 被内联属性覆盖 + assertThat(env.getProperty("app.version")).isEqualTo("test-1.0"); + } +} +``` + +--- + +### 总结与最佳实践 + +| 注解 | 用途 | 何时使用 | +| :--- | :--- | :--- | +| **`@Value`** | 注入单个值 | 当你只需要一两个简单的配置时。 | +| **`@PropertySource`** | 加载额外的属性文件 | 当你的配置分散在多个自定义文件中时。 | +| **`@ConfigurationProperties`** | **批量**、**类型安全**地绑定属性到对象 | **首选方式**。当你有一组相关配置时(如数据库、邮件、API密钥等)。 | +| **`@EnableConfigurationProperties`** | 激活 `@ConfigurationProperties` 的类 | 总是与 `@ConfigurationProperties` 配合使用(除非用了`@Component`快捷方式)。 | +| **`@TestPropertySource`** | 在测试中覆盖或提供配置 | 编写需要特定配置的集成测试或单元测试时。 | + +**最佳实践**: + +* **优先使用 `@ConfigurationProperties`**:对于任何超过两三个的相关配置,都应创建一个专用的 `Properties` 类。这会让你的代码更健壮、更易于维护。 +* **集中管理**: 将 `@EnableConfigurationProperties` 放在主配置类或一个集中的 `AppConfig` 类中,而不是到处分散。 +* **利用元数据**: 添加 `spring-boot-configuration-processor` 依赖到 `pom.xml` 或 `build.gradle`,以获得强大的 IDE 支持。 \ No newline at end of file diff --git a/src/content/posts/中间件/MySQL/数据库ACID四大特性.md b/src/content/posts/中间件/MySQL/数据库ACID四大特性.md new file mode 100644 index 0000000..f37f9a8 --- /dev/null +++ b/src/content/posts/中间件/MySQL/数据库ACID四大特性.md @@ -0,0 +1,68 @@ +--- +title: 数据库ACID四大特性 +published: 2025-08-07 +description: '' +image: '' +tags: [ACID,四大特性] +category: '中间件 > MySQL' +draft: false +lang: '' +--- + + +# 什么是ACID四大特性 +A : Atomicity(原子性) +C : Consistency(一致性) +I : Isolation(隔离性) +D : Durability(持久性) + + +# Atomicity(原子性) +这里要先讲一下什么是事务:  简单说,事务就是一组原子性的SQL执行单元。如果数据库引擎能够成功地对数据库应 用该组査询的全部语句,那么就执行该组SQL。如果其中有任何一条语句因为崩溃或其 他原因无法执行,那么所有的语句都不会执行。要么全部执行成功(commit),要么全部执行失败(rollback)。 + +**原子性指的是一个事务中所有操作要么全部成功要么全部失败。** + +# Consistency(一致性) +数据库的一致性指的是: 每个事务必须使数据库从一个合法的状态,转变到另一个合法的状态,并且在事务执行前后,数据库的各种完整性约束都得以保持。 + +什么是完整性约束呢? +完整性约束是指数据库中数据的规则和限制,比如主键约束、外键约束、唯一性约束等。 +主键约束: 确保每条记录都有唯一标识。 +外键约束: 确保数据之间的引用关系正确。 +唯一性约束: 确保某列的值在表中是唯一的 + +![](https://blog.meowrain.cn/api/i/2025/08/08/f8ed5e-1.webp) + +举个例子: +当我们向订单表插入一条记录的时候,如果指定的`customer_id`在客户表中不存在,那么这个事务就不应该被提交,因为这会破坏数据的一致性。因此,外键约束会阻止事务提交,确保数据库的一致性。抛出外键约束异常 + + +# Isolation(隔离性) +数据库的隔离性指的是: 每个事务的执行都应该是独立的,互不干扰。即使多个事务同时执行,也不会影响彼此的结果。 +隔离性确保了事务之间的独立性,防止了脏读、不可重复读和幻读等问题。 + +如果没有隔离性,在多个用户并发访问数据库的情况下,可能会出现以下问题: +- 脏读(Dirty Read): + 一个事务读取到另一个事务尚未提交的修改,如果该事务回滚,那么读取到的就是无效数据。 +- 不可重复读(Non-repeatable Read): + 一个事务在同一事务内多次读取同一数据,却得到不同的结果,这是因为其他事务修改并提交了数据。 +- 幻读(Phantom Read): + 一个事务在同一事务内多次执行相同的查询,但是每次查询返回的结果集不同,这是因为其他事务插入或删除了数据。 + +![](https://blog.meowrain.cn/api/i/2025/08/08/f82hdp-1.webp) + +事务隔离级别: +- 读未提交 >> 事务可以读取其他事务未提交的数据,可能会导致脏读。 +- 读已提交 >> 事务只能读取已提交的数据,防止脏读。 +- 可重复读 >> 事务在执行期间多次读取同一数据,结果保持一致,防止不可重复读。 +- 串行化 >> 事务完全隔离,按顺序执行,防止幻读。 + + +数据库的默认隔离级别是**可重复读(Repeatable Read)**,它可以防止脏读和不可重复读,但可能会出现幻读。(也就是无法避免读取数据的时候,其他事务提交新的数据或者删除数据,导致查询的结果集发生变化。 + +> 之前老把幻读和不可重复读搞混,现在再讲一下,所谓幻读,就是说读取数据过程中,另外一个数据库事务插入或者删除了数据,导致查询的数据结果集发生变化。而不可重复读是指在同一个事务中多次读取同一个数据,期间其他事务修改了数据,导致数据结果集不一致。 + +每个隔离级别都提供了不同程度的隔离性和性能,具体选择取决于应用场景和需求。 +# Durability(持久性) +数据库的持久性指的是: 一旦事务提交,对数据库的修改就会永久保存,即使系统崩溃也不会丢失。 +持久性确保了数据的可靠性和稳定性,即使在系统故障或崩溃后,已提交的事务数据仍然可以恢复。 \ No newline at end of file diff --git a/src/content/posts/中间件/Redis/redis配置序列化.md b/src/content/posts/中间件/Redis/redis配置序列化.md new file mode 100644 index 0000000..5decf69 --- /dev/null +++ b/src/content/posts/中间件/Redis/redis配置序列化.md @@ -0,0 +1,157 @@ +--- +title: redis配置json序列化 +published: 2025-08-08 +description: '' +image: 'https://blog.meowrain.cn/api/i/2025/07/19/p9zr81-1.webp' +tags: [Redis, 中间件, Redis数据类型] +category: '中间件 > Redis' +draft: false +lang: '' +--- + +# Redis配置序列化 +序列化的最终目的是为了对象可以跨平台存储,进行网络传输。 +Redis默认用的是JdkSerializationRedisSerializer,它使用JDK提供的序列化功能,优点是反序列化的时候不需要提供类型信息,但缺点是序列化后的数据体积较大,性能较低。 +因此,通常会使用更高效的序列化方式,如JSON、Protobuf等 + +Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。 +优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。 + +但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。 通过查看源代码,发现其只在反序列化过程中用到了类型信息。 + +现在的问题是: 如果使用默认的JDK序列化方式,在Redis可视化工具中查看kv值的时候会出现乱码,而使用Jackson2JsonRedisSerializer序列化后,kv值在Redis可视化工具中查看时是正常的。 + + +StringRedisTemplate → Key 和 Value 都是 String 序列化,简单粗暴,适合存验证码、token、计数器、纯 JSON 文本之类的轻量数据。 + +自定义 RedisTemplate → 用了 JSON 序列化器,直接存 Java 对象,取出来就能反序列化成原类型,适合缓存业务对象、集合、复杂结构等。 + + +# 配置 +```java + +package org.example.config; + + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class MyRedisConfig { + + /** + * RedisTemplate 配置类 + * - 使用自定义的 ObjectMapper + GenericJackson2JsonRedisSerializer + * - 实现 key 用字符串序列化,value 用 JSON 序列化 + */ + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + // 创建 RedisTemplate,指定 key 类型为 String,value 类型为 Object + RedisTemplate template = new RedisTemplate<>(); + + // 设置 Redis 连接工厂(由 Spring Boot 配置的连接信息决定) + template.setConnectionFactory(factory); + + // 创建并配置 Jackson 的 ObjectMapper(用于 JSON 序列化/反序列化) + ObjectMapper mapper = new ObjectMapper(); + + // 设置可见性:让所有字段(包括 private)都参与序列化和反序列化 + mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + + // 启用默认类型信息(多态类型序列化) + // NON_FINAL 表示对所有非 final 类(如普通类、接口实现类)写入类型信息 + // 好处:反序列化时可以还原原始对象类型(避免反成 LinkedHashMap) + mapper.activateDefaultTyping( + mapper.getPolymorphicTypeValidator(), // 类型验证器,防止反序列化攻击 + ObjectMapper.DefaultTyping.NON_FINAL // 应用于所有非 final 的类 + ); + + // 创建 JSON 序列化器,并注入自定义的 ObjectMapper + GenericJackson2JsonRedisSerializer serializer = + new GenericJackson2JsonRedisSerializer(mapper); + + // key 采用字符串序列化器,保证可读性(在 Redis CLI 中能直接看到) + template.setKeySerializer(new StringRedisSerializer()); + // value 采用 JSON 序列化器,支持存储任意对象 + template.setValueSerializer(serializer); + + // hash 结构的 key 也用字符串序列化 + template.setHashKeySerializer(new StringRedisSerializer()); + // hash 结构的 value 也用 JSON 序列化 + template.setHashValueSerializer(serializer); + + // 初始化 RedisTemplate 的配置 + template.afterPropertiesSet(); + + return template; + } + +} +``` + +> 什么是反序列化攻击? +反序列化攻击是指攻击者通过构造恶意的序列化数据,利用应用程序在反序列化过程中执行不安全的代码或操作,从而导致安全漏洞。攻击者可以通过发送特制的序列化数据包,触发应用程序执行未授权的操作、获取敏感信息或执行任意代码。 + + + # 使用 +```java +package org.example; + +import org.example.entity.Human; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.data.redis.core.RedisTemplate; + +@SpringBootApplication +public class Main { + public static void main(String[] args) { + ApplicationContext ctx = new SpringApplicationBuilder(Main.class) + .web(WebApplicationType.NONE) // 不启动 Tomcat + .run(args); + + // 按名字拿,避免和 stringRedisTemplate 冲突 + RedisTemplate redisTemplate = + (RedisTemplate) ctx.getBean("redisTemplate"); + + // 打印一下序列化器,确认确实是你配置的 + System.out.println("KeySerializer = " + redisTemplate.getKeySerializer().getClass().getName()); + System.out.println("ValueSerializer = " + redisTemplate.getValueSerializer().getClass().getName()); + + String key = "test:human:" + System.currentTimeMillis(); + Human h = new Human("jackv", "dfasdfssfsdf"); + + redisTemplate.opsForValue().set(key, h); + Object v = redisTemplate.opsForValue().get(key); + + System.out.println("Fetched value class = " + (v == null ? "null" : v.getClass().getName())); + System.out.println("Fetched value = " + v); + + // 简单校验:能拿回对象、类型是你期望的 + if (!(v instanceof Human)) { + throw new IllegalStateException("不是 Human,而是:" + (v == null ? "null" : v.getClass())); + } + // 可选:清理 + redisTemplate.delete(key); + + System.out.println("OK, JSON 序列化/反序列化正常。"); + } +} + +``` + +在其他类的时候直接@Autowired注入RedisTemplate即可使用。 + +```java +@Autowired +@Qualifier("redisTemplate") +private RedisTemplate redisTemplate; +``` \ No newline at end of file