mybatis-plus相关

软删除:

使用标志位进行软删除,通常在数据库表中添加一个deleted字段,标记记录是否被删除。通过在查询时过滤掉deleted字段为true的记录,实现软删除功能。

在MyBatis-Plus中,可以通过@TableLogic注解来实现软删除功能。只需在实体类的deleted字段上添加该注解,MyBatis-Plus会自动处理软删除逻辑。

1
2
3
4
5
6
7
8
9
10
public class User {

private Long id;

private String name;

// 逻辑删除标志: 0代表未删除,1代表已删除
@TableLogic(value = "0", delval = "1")
private Integer deleted;
}

使用:

1
List<User> users = userMapper.selectList(null);

会自动拼接为:

1
2
3
SELECT id, name, type, deleted
FROM user
WHERE deleted = 0

在数据库中查询时,使用mybaits plus,Ipage有可能返回null吗?还是说只会返回零条记录?

在使用MyBatis Plus进行分页查询时,如果查询结果为空,则返回的IPage对象中的total属性为0,而不是null。

如果查找不到记录,使用gettRecords()方法获取list得到的结果为[];

请求和响应

在请求和响应中,有一些注解可以帮助我们更好地处理数据:

1
2
3
4
@RequestBody // 用于将请求体中的JSON数据转换为Java对象
@ResponseBody // 用于将Java对象转换为JSON格式的响应体
@PathVariable // 用于从URL路径中提取变量
@RequestParam // 用于从请求参数中提取数据

在请求和响应中vo对象中,有一些注解可以帮助我们更好地处理数据:

1
2
3
4
5
6
@JsonFormat // 用于格式化日期和时间
@Max // 用于指定字段的最大值
@Min // 用于指定字段的最小值
@NotBlank // 用于验证字符串字段不能为空
@NotNull // 用于验证字段不能为空
@Size // 用于验证字符串或集合的大小

在写请求的vo时,应该去校验前端返回的字段是否正确。
比如:

1
2
3
4
public class UserRequestVO {
@NotBlank(message = "用户名不能为空")
private String username;
}

使用validator去校验,并把错误信息返回给前端:

1
2
3
4
5
6
7
8
Set<ConstraintViolation<UserRequestVO>> violations = validator.validate(userRequestVO);
if (!violations.isEmpty()) {
StringBuilder errorMessage = new StringBuilder();
for (ConstraintViolation<UserRequestVO> violation : violations) {
errorMessage.append(violation.getMessage()).append("; ");
}
throw new IllegalArgumentException(errorMessage.toString());
}

在action中,使用@Valid注解,自动校验请求参数

1
2
@PostMapping("/users")
public ResponseEntity<Void> createUser(@Valid @RequestBody UserRequestVO userRequestVO) {}

对枚举值进行校验

示例枚举类:

1
2
3
4
5
6
7
8
public enum AudioType {
NORMAL(0, "普通音频"),
TRIAL(1, "试听音频"),
VIP(2, "会员音频");

private final int code;
private final String desc;
}

创建一个枚举值校验器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = EnumValidator.class)
public @interface EnumValid {
String message() default "枚举值不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};

Class<? extends Enum<?>> enumClass();
}

public class EnumValidator implements ConstraintValidator<EnumValid, Object> {
private Class<? extends Enum<?>> enumClass;

@Override
public void initialize(EnumValid annotation) {
this.enumClass = annotation.enumClass();
}

@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (value == null) return true; // 允许空
for (Enum<?> e : enumClass.getEnumConstants()) {
if (e.name().equals(value.toString())) return true;
}
return false;
}
}

IDEA配置

自动导包

File -> Settings -> Editor -> General -> Auto Import -> Add unambiguous imports on the fly

自动格式化代码

File -> Settings -> Editor -> Code Style -> Java -> Formatter Control -> Enable formatter markers in comments

自动删去无用的包

File -> Settings -> Editor -> General -> Auto Import -> Optimize imports on the fly

自动添加类描述

File -> Settings -> Editor -> File and Code Templates -> Class -> 在模板开头添加注释

异常处理

不能抛出非受检异常,应该定义自定义异常,常见的:BizException、RuntimeException等

1
2
3
public void someMethod() throws BizException {
// 业务逻辑
}

全局异常处理

代码规范

1、抛出异常的地方都需要记录日志
2、日志的格式:时间+类名+方法名+异常信息:

1
log.error("时间:{},类名:{},方法名:{},异常信息:{}", LocalDateTime.now(), this.getClass().getName(), "方法名", e.getMessage());

3、如果需要有空返回,先返回空,而不是先走业务逻辑

Spring Boot相关

@Value注解

为什么@Value注解可以注入配置文件中的值?

Spring 在创建 Bean 时,会使用专门的处理器(BeanPostProcessor)扫描 @Value 注解,把 ${xxx} 转成真正的配置值,然后通过反射赋值给字段。

这背后分三步:

1. Spring 会加载配置文件进 Environment

application.yml / application.properties
➡ 会被 Spring 解析成一堆 key-value
➡ 存进 Environment(它是所有配置的统一容器)

例如:

1
app.name: demo

Spring 会存成:

1
Environment["app.name"] = "demo"
2. Spring 创建 Bean 时,会扫描字段上的 @Value

Spring 在创建 Bean 时,会用到一个特殊的处理器:

1
AutowiredAnnotationBeanPostProcessor

它会检查字段上是否有:

  • @Autowired
  • @Value
  • @Resource
  • ……等

看到 @Value("${app.name}")
➡ 它会把 ${app.name} 交给 PlaceholderResolver 去解析。

3. Spring 解析占位符并通过反射把值注入字段

${app.name}
➡ 解析得到 "demo"
➡ 反射赋值给字段:

1
this.name = "demo";

Bean 就成功拿到配置文件中的值了。

配置的优先级

  1. 命令行参数
  2. Java 系统属性(-Dkey=value)
  3. OS 环境变量
  4. jar 外部的 application.yml / properties
  5. jar 内部(/resources)的 application.yml / properties
  6. @PropertySource 指定的配置文件
  7. 默认配置(Spring Boot 自动配置设置的默认值)

启动参数

Java和 Spring Boot 常用启动参数:

1
2
3
4
5
6
7
java -Xms1g -Xmx2g \
-Denv=prod \
-Duser.timezone=Asia/Shanghai \
-jar app.jar \
--spring.profiles.active=prod \
--server.port=9000 \
--logging.level.root=INFO

Serializable

在 Java 中,实现 Serializable 接口的类可以将其实例转换为字节流,从而实现对象的持久化存储或通过网络传输。

serialVersionUID 的作用

serialVersionUID 是 Java 序列化机制中的一个重要概念。它是一个唯一的标识符,用于验证序列化和反序列化过程中类的版本一致性。

  • 当一个对象序列化后写入文件或缓存,再次反序列化时,JVM 会检查类的 serialVersionUID 是否一致。
  • 如果类结构改变(新增/删除字段等),但 serialVersionUID 没变,可能导致 序列化不兼容问题。
  • 如果没有显式定义,JVM 会根据类的结构生成一个默认的 serialVersionUID,但类结构一改,生成的值会变,导致反序列化失败。

事务

在 Spring 中,事务管理通常通过 @Transactional 注解来实现。它可以应用于类或方法上,以指定该类或方法需要在事务上下文中执行。

注意事项

  1. @Transactional 必须加在 public 方法上,才一定生效
  2. 同一个类内部方法调用(self-invocation)不会触发事务
  3. 事务方法应该放在 Service 层,而不是 Controller

正常调用的链路

1
2
3
4
5
6
7
8
@Service
public class OrderService {

@Transactional
public void createOrder() {
...
}
}

外部调用实际执行的是:

1
2
3
4
OrderService$$Proxy.createOrder()
→ 开启事务
→ 调用 OrderService.createOrder()
→ 提交 / 回滚事务

同一个类的内部调用:

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class OrderService {

public void methodA() {
methodB(); // ❌ 内部调用
}

@Transactional
public void methodB() {
...
}
}

实际执行的是:

1
2
OrderService.methodA()
→ this.methodB()

此时不会触发事务,完全绕开了Spring AOP,因为没有经过代理对象。

拆成新的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class OrderService {

@Autowired
private OrderApplyService applyService;

public void process() {
applyService.apply(); // ✔ 代理调用
}
}

@Service
public class OrderApplyService {

@Transactional
public void apply() {
...
}
}

实际执行是:

1
OrderService → OrderApplyService$$Proxy → apply()

tips

  1. 构造器注入优于@Autowired注入,推荐使用构造器注入。
  2. 少用 @Component 扫一切
    1. 推荐使用 @Service、@Repository、@Controller 等更具体的注解,明确类的角色和职责。