**【建议】POJO类属性建议一律为包装类型,而且不要设置任何属性默认值
**
如果在开发中,我们对POJO类属性设置了默认值(包装类型显示设置默认值,基本类型编译期推导默认值),难免会遇到一些"坑"。
坑:反序列化可能导致默认值可以被null覆盖
以jackson为例:
package com.renzhikeji.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.builder.ToStringBuilder;
/**
* @author 认知科技技术团队
* 微信公众号:认知科技技术团队
*/
public class Demo {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"id\":null,\"name\":null,\"address\":\"\"}";
POJO value = mapper.readValue(json, POJO.class);
System.out.println(value);
}
public static class POJO {
private Long id = 0L;
private String name;
private String address = "";
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("address", address)
.toString();
}
}
}
POJO定义中,id默认值为0,当我们的反序列化字符串为:
{"id":null,"name":null,"address":""}
json字符串中id设置为null的时候:结果反序列化后,id的默认值就没有了。
坑:POJO与DTO之间转换,一个为原生类型,一个是对应的包装类型,使用类似BeanUtils.copyProperties的工具复制对象可能会抛出异常
以org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)为例(spring-beans-5.3.21):
package com.renzhikeji.demo;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.BeanUtils;
/**
* @author 认知科技技术团队
* 微信公众号:认知科技技术团队
*/
public class Demo {
public static void main(String[] args) throws JsonProcessingException {
POJO pojo = new POJO();
pojo.setId(null);
DTO dto = new DTO();
BeanUtils.copyProperties(pojo, dto);
System.out.println(dto);
}
public static class POJO {
private Long id = 0L;
private String name;
private String address = "";
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("id", id).append("name", name).append("address", address).toString();
}
}
public static class DTO {
private long id = 0L;
private String name;
private String address = "";
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 String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return new ToStringBuilder(this).append("id", id).append("name", name).append("address", address).toString();
}
}
}
主要区别是:同一个属性名,但是一个是原生类型,一个是对应的包装类型,原生类型的本来意愿是不传值,就有个默认值,可惜,事与愿违:
**坑:MyBatis的动态sql中,可能遇到
**
MyBatis动态sql中,如果遇到如下所示类似的动态sql,title或author没有显示设置,POJO中都设置了默认值,动态sql就失去了意义,业务逻辑就错了。
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
AND title like #{title}
AND author_name like #{author.name}
**坑:API接口或RPC接口返回默认值,造成业务逻辑bug
**
拿部门同事的计费系统的一个扣费异常为例,扣费时需要通过 RPC 请求计费系统得到一个费率值,预期该接口的返回值中会包含一个浮点型的费率字段。
当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。
如果RPC返回该费率值时可能由于bug或其他业务原因没有设置,拿到默认值0.0就进行计算,不会进行扣费,这种扣费为0的异常情况无法被感知(当然你可以把这种业务情况发报警),但是如果不用原生类型或不设置包装类型默认值,以null参与计算直接抛出异常报警,是不是更容易发现。
小结
POJO类属性建议一律为包装类型,而且不要设置任何属性默认值,以上的坑只是冰山一角。