本文旨在分享我在日常开发中总结的经验和注意事项。通过这篇文章,我希望能够帮助读者们更好地应对开发过程中的各种问题,并提供一些建议和解决方案。
注意 Long 类型转换
比如说前端需要字符串类型的id,而数据库中存放的是 Long 类型,在 modelToVo 时确实可以将 Long 转为 String,但是步骤多此一举,而且内部使用不方便,直接使用 Long 类型更好。
public class AiCreationRecordVo {
@JsonDeserialize(using = LongJsonDeserializer.class)
@JsonSerialize(using = LongJsonSerializer.class)
private Long recordId;
}
扩展:如果返回给前端String 类型 id,当前端调用get请求,将 String 类型 id作为 queryParam 传进来,那么没必要用 String类型接收,直接用 Long 类型接收就可以了。核心原因是 Spring 自带的 TypeConverterDelegate,可以将属性进行转换。
@GetMapping("/info")
public ApiResult getInfo(@RequestParam Long id, HttpServletRequest request)
同理,dto 类中的 Long 类型是否需要特殊处理,比如下面这样:
@JsonSerialize(using = LongJsonSerializer.class)
@JsonDeserialize(using = LongJsonDeserializer.class)
private Long articleId;
反序列化代码如下:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class LongJsonDeserializer extends JsonDeserializer {
private static final Logger logger = LoggerFactory.getLogger(LongJsonDeserializer.class);
@Override
public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getText();
try {
return value == null || value.length() == 0 ? null : Long.valueOf(value);
} catch (NumberFormatException e) {
logger.error("LongJsonDeserializer.deserialize failed:{},cause:{}", e.getMessage(), e.getCause());
throw new RuntimeException("LongJsonDeserializer.deserialize failed");
}
}
}
前端传参如下:
{
"articleId":"1633271396750176257",
"wordCount":"10"
}
如果不加@JsonDeserialize,代码也可以正常运行,因为背后有 fasterxml 来处理。
首先是 com.fasterxml.jackson.databind.deser.BeanDeserializer 文件的 deserialize 方法,接着调用 com.fasterxml.jackson.databind.deser.impl.MethodProperty 文件的 deserializeAndSet 方法,因为我们这里是 String 转 Long,最后调用 com.fasterxml.jackson.databind.deser.std.NumberDeserializers 文件内部实现类 LongDeserializer,其中有个 deserialize 方法。
可以发现,本质上都是通过 fasterxml 来解决序列化问题的,经过循环接口调用测试,发现两者耗时差不多。
swagger 注解的使用
controller 层使用 swagger 注解,请求体也要使用
@Api(tags = "AI创作记录")
@ApiOperation(value = "用户生成论文记录分页查询")
@ApiModel
public class CreationRecordQueryDto extends BaseQueryDto {
@ApiModelProperty(value = "用户id", hidden = true)
private String userId;
}
有些时候 dto 中的字段不是前端需要的字段,那么在 swagger 中隐藏。
@ApiModelProperty(hidden = true)
private String ip;
关于Serializable的使用
dto、model、vo 三者是否都需要实现 Serializable?答案是不一定。
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
什么情况下需要序列化:
- 当你想把内存中的对象状态保存到一个文件中或者数据库中的时候;
- 当你想用套接字在网络上传送对象的时候;注意啊,并不是我们平时简单的前后台数据交互,涉及到 ObjectInputStream 和 ObjectOutputStream 对象,包括 readObject 和 writeObject,代码示例参考本文。
- 当你想通过 RMI 传输对象的时候;实现起来同样复杂,我们平时基本很少接触,推荐阅读本文。
综上,Model 因为要与数据库打交道,所以必须要实现 Serializable。dto 和 vo 并不需要实现该接口。
Spring 默认使用的 json 解析工具就是 jackson,所以与 Serializable 不相关。
有的时候并没有实现序列化,依然可以持久化到数据库:
这个其实我们可以看看实体类中常用的数据类型,例如 Date、String 等等,它们已经实现了序列化,而一些基本类型,数据库里面有与之对应的数据结构,从我们的类声明来看,我们没有实现 serializabel接口 ,其实是在声明的各个不同变量的时候,由具体的数据类型帮助我们实现了序列化操作。
推荐阅读:你的JavaBean是否真的需要实现Serializable
关于Spring事务的使用
一般情况下,关于参数的校验,我们都是在 service 层处理,如果刚好方法开启了事务,那么一旦参数校验失败返回,是否会浪费这个事务呢?答案是不会的,原因如下:
@Transactional注解是开启事务的一种方式。当您在Spring中使用@Transactional注解时,它会创建一个代理对象作为Bean。当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解。如果加了,那么则利用事务管理器创建一个数据库连接,并且修改数据。
Spring框架通过TransactionInterceptor类与MySQL数据库的事务结合,TransactionInterceptor是Spring框架内置实现的一个MethodInterceptor,用于声明式事务管理,使用Spring事务基础设施org.springframework.transaction.PlatformTransactionManager。
我们基于MySQL数据库进行分析,当加了@Transcational注解后,进入该方法时,相当于执行 begin 或 start transaction,配套的提交语句是 commit,回滚语句是 rollback。begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。
综上,即使将参数校验加在 service 的方法中,也不会影响性能。反之,在Spring中,如果您随意开启事务,可能会导致性能问题。这是因为事务会导致数据库锁定,从而影响性能。此外,如果您的事务范围比您预期的更大,则可能会导致性能问题。
关于上述内容的深入讲解,推荐阅读 why哥的这篇文章。
应用场景:
注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务@Transcational(readOnly=true)
注意Map与Json转换
当一个Map被转成Json字符串后,被添加到另一个Map中,如果这个新的Map需要转成Json字符串格式,那么转化后,内部的这个Map转成的Json字符串,都会被加上“\”转义字符。
public static void main(String[] args) {
HashMap param = new HashMap();
param.put("userId", 66666);
param.put("username", "XXXX");
HashMap pushMap = new HashMap();
pushMap.put("testKey01", "value01");
pushMap.put("testKey02", "value02");
param.put("pushJson", JSON.toJSONString(pushMap));
String pushJson = JSON.toJSONString(param);
System.out.println(pushJson);
}
执行结果为:
{
"pushJson":
"{\"testKey01\":\"value01\",\"testKey02\":\"value02\"}",
"userId":66666,
"username":"XXXX"
}
这种情况下考虑是否可以直接将map 对象设置进去,而不需要转为 String。
更多内容推荐阅读:文章1。文章2
MybatisPlus XML文件注意事项
1、Integer 查询
当查询条件为 Integer 类型时,如果值为0,则下面这个判断为false。
AND c.charge_status = #{chargeStatus}
经测试发现mybatis的 if 将 0 认为是 ’ ’ ,所以这样判断是无法进入条件的,但是将数字换为非 0 就可以了;
改为:
AND c.charge_status = #{chargeStatus}
通过源码了解到,mybatis在预编译sql时,使用OGNL表达式来解析if标签,
对于Integer类型属性,在判断不等于’‘时,例如age != ‘’,OGNL会返回’'的长度,源码:(s.length() == 0) ? 0.0 : Double.parseDouble( s ),因此表达式age != ''被当做age != 0来判断,所以当age为0时,if条件判断不通过。
2、布尔值查询
最开始的写法:
and ur.is_paid = #{dto.paid}
但是上述判断对于 paid 为 false 时失效,仍然会查询出 true 的数据。
可以考虑这样写:
and ur.is_paid = 1
and ur.is_paid = 0
总结
我深信,通过共享经验和知识,我们能够共同进步。无论你是刚入门的新手还是经验丰富的老手,我都希望这些经验总结能够为你提供一些帮助和灵感。
同时,我将不断更新文章的内容,以保持与时俱进。