Skip to content

Commit 404a4c1

Browse files
committed
dailyDev
1 parent a23c7db commit 404a4c1

File tree

1 file changed

+350
-0
lines changed

1 file changed

+350
-0
lines changed

src/develop/DailyDev.md

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
# 日常开发笔记
2+
3+
## 2025/10/21 - MyBatis-Plus JSON字段查询返回null问题
4+
5+
### 问题
6+
使用 MyBatis-Plus 查询时,实体类中配置了 TypeHandler 的 JSON 字段始终返回 null,但新增/编辑操作正常。
7+
8+
### 原因
9+
- MyBatis-Plus 默认不为实体类生成 ResultMap
10+
- 没有 ResultMap 时,字段上的 `typeHandler` 配置在查询时不生效
11+
- SQL 日志显示该字段被识别为 `<<BLOB>>` 类型
12+
13+
### 解决方法
14+
在实体类的 `@TableName` 注解中添加 `autoResultMap = true`
15+
16+
```java
17+
@TableName(value = "table_name", autoResultMap = true)
18+
public class Entity {
19+
@TableField(typeHandler = JacksonTypeHandler.class)
20+
private List<String> jsonField;
21+
}
22+
```
23+
24+
### 关键点
25+
- `autoResultMap = true` 让 MyBatis-Plus 自动生成 ResultMap
26+
- 生成的 ResultMap 会正确应用字段上的 typeHandler 配置
27+
- 写入时 typeHandler 默认生效,但查询时需要 ResultMap 支持
28+
29+
30+
31+
## 2025/10/21 - Spring的@Transactional注解
32+
33+
`@Transactional` 是 Spring 框架提供的声明式事务管理注解,用于控制数据库事务的行为。
34+
35+
### 主要作用
36+
- 自动管理事务的开启、提交和回滚
37+
- 确保方法内的数据库操作要么全部成功,要么全部失败(原子性)
38+
39+
### 常用属性
40+
41+
#### 1. `rollbackFor`
42+
指定哪些异常会触发事务回滚:
43+
```java
44+
@Transactional(rollbackFor = Exception.class) // 所有 Exception 都回滚
45+
```
46+
47+
#### 2. `propagation`
48+
事务传播行为:
49+
- `REQUIRED`(默认):如果当前存在事务,则加入;否则创建新事务
50+
- `REQUIRES_NEW`:总是创建新事务,挂起当前事务
51+
- `NESTED`:嵌套事务,支持部分回滚
52+
53+
#### 3. `isolation`
54+
事务隔离级别:
55+
- `READ_UNCOMMITTED`:最低隔离级别,可能脏读
56+
- `READ_COMMITTED`:防止脏读
57+
- `REPEATABLE_READ`(MySQL默认):防止脏读和不可重复读
58+
- `SERIALIZABLE`:最高隔离级别,完全串行化
59+
60+
#### 4. `readOnly`
61+
标记为只读事务(优化性能):
62+
```java
63+
@Transactional(readOnly = true)
64+
```
65+
66+
#### 5. `timeout`
67+
事务超时时间(秒):
68+
```java
69+
@Transactional(timeout = 30)
70+
```
71+
72+
### 使用位置
73+
- **类级别**:对该类的所有 public 方法生效
74+
- **方法级别**:仅对该方法生效(会覆盖类级别配置)
75+
76+
### 注意事项
77+
1. **只对 public 方法生效**
78+
2. **自调用失效**:同一个类内部方法调用不会触发事务(因为没有走代理)
79+
3. **异常类型**:只写 `@Transactional` 不加任何参数,默认只对 `RuntimeException``Error` 回滚,checked 异常不会回滚(需要指定 `rollbackFor`
80+
81+
82+
83+
84+
85+
## 2025/10/21 - 分页表格跨页选择数据丢失问题
86+
87+
### 问题
88+
在使用 Ant Design Vue 的表格组件实现多选功能时,发现每次翻页后,之前页面中已选择的数据会被清空,无法实现跨页面的多选保持。
89+
90+
### 问题
91+
Ant Design Vue 的 `<a-table>` 组件在数据源(`dataSource`)发生变化时,默认会重置 `selectedRowKeys`。当用户翻页时,虽然视觉上看起来是同一个表格,但实际上表格的 `dataSource` 已经更新为新的数据,这会触发组件的内部重置逻辑,导致之前的选择状态丢失。
92+
93+
### 解决方案
94+
95+
#### 1. 启用跨页选择保持
96+
在表格的 `:row-selection` 配置中添加 `preserveSelectedRowKeys: true`
97+
98+
```vue
99+
<a-table
100+
:columns="columns"
101+
:data-source="dataList"
102+
:row-selection="{
103+
selectedRowKeys: selectedKeys,
104+
onChange: handleSelectionChange,
105+
preserveSelectedRowKeys: true // 关键配置:保持选中的 key
106+
}"
107+
:pagination="paginationConfig"
108+
/>
109+
```
110+
111+
#### 2. 使用数组展开避免引用问题
112+
在选择变化的回调函数中,使用数组展开运算符创建新数组,而不是直接引用:
113+
114+
```javascript
115+
// ❌ 错误写法 - 直接引用
116+
const handleSelectionChange = (keys) => {
117+
formData.value.selectedIds = keys
118+
}
119+
120+
// ✅ 正确写法 - 创建新数组
121+
const handleSelectionChange = (keys) => {
122+
formData.value.selectedIds = [...keys]
123+
}
124+
```
125+
126+
#### 3. 初始化数组避免 undefined 错误
127+
在表单打开或初始化时,确保选中的数组字段被初始化为空数组:
128+
129+
```javascript
130+
const openForm = () => {
131+
formData.value = {
132+
selectedIds: [], // 初始化为空数组,避免 undefined
133+
// ...其他字段
134+
}
135+
}
136+
```
137+
138+
### 完整示例
139+
140+
```vue
141+
<template>
142+
<a-table
143+
:columns="columns"
144+
:data-source="dataList"
145+
:row-selection="{
146+
selectedRowKeys: formData.selectedIds,
147+
onChange: handleSelectionChange,
148+
preserveSelectedRowKeys: true
149+
}"
150+
:pagination="{
151+
current: pagination.current,
152+
pageSize: pagination.pageSize,
153+
total: pagination.total,
154+
showSizeChanger: true,
155+
showTotal: (total) => `共 ${total} 条`
156+
}"
157+
@change="handleTableChange"
158+
/>
159+
</template>
160+
161+
<script setup>
162+
import { ref, watch } from 'vue'
163+
164+
const formData = ref({
165+
selectedIds: []
166+
})
167+
168+
const dataList = ref([])
169+
const pagination = ref({
170+
current: 1,
171+
pageSize: 10,
172+
total: 0
173+
})
174+
175+
// 选择变化处理
176+
const handleSelectionChange = (selectedRowKeys) => {
177+
formData.value.selectedIds = [...selectedRowKeys]
178+
}
179+
180+
// 分页变化处理
181+
const handleTableChange = (paginationInfo) => {
182+
pagination.value.current = paginationInfo.current
183+
pagination.value.pageSize = paginationInfo.pageSize
184+
loadData()
185+
}
186+
187+
// 加载数据
188+
const loadData = async () => {
189+
const response = await api.getData({
190+
current: pagination.value.current,
191+
size: pagination.value.pageSize
192+
})
193+
dataList.value = response.data.records
194+
pagination.value.total = response.data.total
195+
}
196+
</script>
197+
```
198+
199+
### 关键要点
200+
201+
1. **preserveSelectedRowKeys**: 这是核心配置,告诉 Ant Design 在数据源变化时保持已选择的 key
202+
2. **数组展开**: 使用 `[...array]` 创建新数组引用,确保 Vue 的响应式系统能正确追踪变化
203+
3. **初始化**: 确保数组字段初始化为空数组而不是 undefined,避免运行时错误
204+
4. **rowKey**: 确保表格数据有唯一的 key 字段(默认是 `key`,也可以通过 `:row-key` 指定)
205+
206+
### 适用场景
207+
208+
- 需要跨页面选择多条记录的场景
209+
- 批量操作功能(如批量删除、批量导出等)
210+
- 复杂表单中的关联数据选择
211+
212+
213+
214+
## Spring 异步方法不生效问题排查笔记
215+
216+
### 问题
217+
218+
在使用 Spring 的 `@Async` 注解实现异步方法时,发现方法并未真正异步执行。通过日志观察,异步方法与调用方法的线程名称相同,都在 HTTP 请求线程中执行,而不是在独立的异步线程池中运行。
219+
220+
**现象:**
221+
- 调用方法日志线程:`[http-nio-82-exec-2]`
222+
- 异步方法日志线程:`[http-nio-82-exec-2]` (期望应该是 `[task-1]` 等异步线程)
223+
- HTTP 请求阻塞等待任务完成,导致超时错误
224+
225+
### 原因
226+
227+
Spring 的 `@Async` 注解基于 AOP 代理实现,存在以下限制条件:
228+
229+
#### 1. 缺少启用注解
230+
Spring Boot 应用需要在配置类或启动类上添加 `@EnableAsync` 注解来启用异步功能。
231+
232+
#### 2. 方法修饰符限制
233+
**Spring AOP 无法代理 private 方法**,因为:
234+
- Spring 使用 CGLIB 或 JDK 动态代理
235+
- **代理只能拦截 public 方法**
236+
- private 方法无法被子类覆盖,AOP 切面无法介入
237+
238+
#### 3. 自调用问题
239+
在同一个类内部直接调用异步方法(`this.asyncMethod()`)会绕过代理,导致 `@Async` 失效。
240+
241+
### 解决方案
242+
243+
#### 步骤 1:启用异步支持
244+
245+
在 Spring Boot 启动类添加 `@EnableAsync` 注解:
246+
247+
```java
248+
@SpringBootApplication
249+
@EnableAsync // 启用异步功能
250+
public class Application {
251+
public static void main(String[] args) {
252+
SpringApplication.run(Application.class, args);
253+
}
254+
}
255+
```
256+
257+
#### 步骤 2:修改方法访问修饰符
258+
259+
将异步方法的访问修饰符从 `private` 改为 `public`
260+
261+
```java
262+
// ❌ 错误:private 方法,AOP 无法代理
263+
@Async
264+
private void processAsync() {
265+
// 异步逻辑
266+
}
267+
268+
// ✅ 正确:public 方法,AOP 可以代理
269+
@Async
270+
public void processAsync() {
271+
// 异步逻辑
272+
}
273+
```
274+
275+
#### 步骤 3:验证异步执行
276+
277+
通过日志确认异步方法在独立线程中执行:
278+
279+
```java
280+
public void callMethod() {
281+
log.info("调用方法,线程:{}", Thread.currentThread().getName());
282+
// 输出:调用方法,线程:http-nio-82-exec-2
283+
284+
asyncMethod();
285+
log.info("已提交异步任务");
286+
}
287+
288+
@Async
289+
public void asyncMethod() {
290+
log.info("异步方法执行,线程:{}", Thread.currentThread().getName());
291+
// 输出:异步方法执行,线程:task-1
292+
}
293+
```
294+
295+
### 异步方法写法
296+
297+
#### 1. 自定义线程池
298+
299+
默认情况下,Spring 使用 `SimpleAsyncTaskExecutor`,建议自定义线程池:
300+
301+
```java
302+
@Configuration
303+
@EnableAsync
304+
public class AsyncConfig implements AsyncConfigurer {
305+
306+
@Override
307+
public Executor getAsyncExecutor() {
308+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
309+
executor.setCorePoolSize(5);
310+
executor.setMaxPoolSize(10);
311+
executor.setQueueCapacity(100);
312+
executor.setThreadNamePrefix("async-task-");
313+
executor.initialize();
314+
return executor;
315+
}
316+
}
317+
```
318+
319+
#### 2. 异常处理
320+
321+
异步方法的异常不会传播到调用方,需要自定义异常处理器:
322+
323+
```java
324+
@Configuration
325+
@EnableAsync
326+
public class AsyncConfig implements AsyncConfigurer {
327+
328+
@Override
329+
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
330+
return (ex, method, params) -> {
331+
log.error("异步方法执行异常 - 方法: {}, 参数: {}",
332+
method.getName(), Arrays.toString(params), ex);
333+
};
334+
}
335+
}
336+
```
337+
338+
#### 3. 返回值处理
339+
340+
异步方法可以返回 `Future``CompletableFuture``ListenableFuture`
341+
342+
```java
343+
@Async
344+
public CompletableFuture<String> asyncMethodWithReturn() {
345+
// 执行异步任务
346+
String result = doSomething();
347+
return CompletableFuture.completedFuture(result);
348+
}
349+
```
350+

0 commit comments

Comments
 (0)