目录
0. 建议 : 多用组合 , 少用继承
1. @EqualsAndHashCode(callSuper = true) - 解决允许调用父类
2. 序列化 ID : private static final long serialVersionUID = 1L;
1. serialVersionUID 作用 : 序列化时为了保持版本的兼容性
3. 数据概览 ( 统计 ) : XxxxProfileVO
1. 统计数据添加默认值 0 - ( 避免返回 null )
4. dto-vo 层添加 Swagger 注释 - 方便前端查看 - ★
5. Java 反射 + 注解 : 实现 Entity 类与 Dto 类相互转换
1. BeanUtils.copyProperties(iotHouseDTO,iotHouse);
6. 警告 : VO 数据 赋值 问题
1. 通过继承得到的父类私有 ( private ) 属性数据 , 需 get / set 访问
/**
* @author menghuan
* @since 2020-04-20
*/
@Data
@ApiModel(value = "会议信息传输对象")
@EqualsAndHashCode(callSuper = true)
public class IotMeetingDTO extends IotMeeting implements Serializable {
private static final long serialVersionUID = 1L;
}
DTO 修改为 VO 的操作:
1、删除 @EqualsAndHashCode(callSuper = true)
2、将传输对象 - 改为 - 展示对象
/**
* @author menghuan
* @since 2020-04-20
*/
@Data
@ApiModel(value = "会议信息展示对象")
public class IotMeetingVO extends IotMeeting implements Serializable {
private static final long serialVersionUID = 1L;
}
在类是继承父类的情况下:
EqualsAndHashCode 实则就是在比较两个对象的属性;
当 @EqualsAndHashCode(callSuper = false) 时,不会比较其继承的父类的属性可能会导致错误判断;
当 @EqualsAndHashCode(callSuper = true) 时,会比较其继承的父类的属性;
1、此注解会生成equals(Object other) 和 hashCode()方法。
2、它默认使用非静态 , 非瞬态的属性
3、可通过参数 exclude 排除一些属性
4、可通过参数 of 指定仅使用哪些属性
5、它默认仅使用该类中定义的属性 , 且不调用父类的方法
6、可通过
callSuper=true
解决上一点问题。让其生成的方法中调用父类的方法。
@Data 相当于 @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode
这5个注解的合集
在 java 对象序列化时 , 如果没有设置 SerialversionUID , 他会给一个默认的值 , 但是通常是建议设置默认的建议自定义一个serialVersionUID ,
因为默认的 serialVersinUID 对于 class 的细节非常敏感 , 反序列化时可能会导致 InvalidClassException 这个异常。
如果没有设置 SerialversionUID 值的话 , 在序列化后反序列化前对类进行了修改 , 类对应的 SerialversionUID 也会变化 , 而序列化和反序列化就是通过对比其SerialversionUID 来进行的 , 一旦 SerialversionUID 不匹配 , 反序列化就无法成功。
详见《Java primary concepts (概念)》
serialVersionUID 作用 :
序列化时为了保持版本的兼容性 , 即在版本升级时反序列化仍保持对象的唯一性。
serialVersionUID 用作 Serializable 类中的版本控件。
如果您没有显式声明 serialVersionUID , JVM 将根据您的 Serializable 类的各个方面自动为您执行此操作 ,
如 : Java(TM)对象序列化规范中所述。
/**
* 应急事件概览展示对象
*
* @author makejava
* @since 2020-10-13 09:35:04
*/
@Data
@ApiModel(value = "应急事件概览展示对象")
public class EventProfileVO {
private static final long serialVersionUID = 1L;
/** 应急事件总数统计 */
private Integer sumNumber;
/** 未处理数量统计 */
private Integer waitDealCount;
/** 已处理数量统计 */
private Integer hasDealCount;
/** 待核实数量统计 */
// private Integer waitVerifyCount;
/** 处理中数量统计 */
// private Integer dealingCount;
/** 已结束数量统计 */
// private Integer finishCount;
}
/**
* @Description 资源概览
* @Author: menghuan
* @Date: 2020/9/10 10:27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ResourceProfileVO {
private static final long serialVersionUID = 1L;
/** 资源(对应状态)统计 */
private Map resourceCount;
/** 资源(对应状态)占比统计 */
private List resourcePercent;
/** 资源上架趋势统计分析 */
private Map resourceTrend;
/** 资源租售势统计分析 */
private Map rentTrend;
/** 设施设备/资源类型 柱状图 */
private Map facilitiesCount;
}
思路 1 : 实体类 引用 (new XxxEntity) 时 , 便为指定属性添加自定义默认值
思路 2 : 对结果进行
if else 判null 赋值 0
处理优化 : 使用三元表达式
for (ResourceMapVO resourceMapVO : resourceMapVOS) {
/*resourceMapVO.setWaitCheckCount(waitCheckCount.get(resourceMapVO.getStructureIdentity()));
resourceMapVO.setWaitRentCount(waitRentCount.get(resourceMapVO.getStructureIdentity()));
resourceMapVO.setHasRentCount(hasRentCount.get(resourceMapVO.getStructureIdentity()));
resourceMapVO.setHasRemoveCount(hasRemoveCount.get(resourceMapVO.getStructureIdentity()));
resourceMapVO.setAddressPath(addressPathInfo.get(resourceMapVO.getStructureIdentity()));*/
resourceMapVO.setWaitCheckCount(null != waitCheckCount.get(resourceMapVO.getStructureIdentity()) ? waitCheckCount.get(resourceMapVO.getStructureIdentity()) : 0);
resourceMapVO.setWaitRentCount(null != waitRentCount.get(resourceMapVO.getStructureIdentity()) ? waitRentCount.get(resourceMapVO.getStructureIdentity()) : 0);
resourceMapVO.setHasRentCount(null != hasRentCount.get(resourceMapVO.getStructureIdentity()) ? hasRentCount.get(resourceMapVO.getStructureIdentity()) : 0);
resourceMapVO.setHasRemoveCount(null != hasRemoveCount.get(resourceMapVO.getStructureIdentity()) ? hasRemoveCount.get(resourceMapVO.getStructureIdentity()) : 0);
resourceMapVO.setAddressPath(addressPathInfo.get(resourceMapVO.getStructureIdentity()));
if (gisInfo.get(resourceMapVO.getStructureIdentity()) != null) {
resourceMapVO.setGisMap(gisInfo.get(resourceMapVO.getStructureIdentity()));
}else{
resourceMapVO.setGisMap("[]");
}
}
思路 3 : 执行统计 sql 直接添加 默认值 : 0
@ApiModelProperty(value = "应急事件总数统计" , name = "sumNumber")
序言
近期在工作中管理代码时发现,在项目中从 Dao 层到 Service 层数据传递中通过大量的 get() , set() 方法去一个一个的去拿值去赋值,导致代码篇幅过长,对此甚是讨厌,并且严重消耗开发时间。
起初找过些关于这块的资料,现在大部分都是 Entity 类和 Dto 类的属性名相同的前提下,利用反射实现,太局限了,如果要改成同名,按目前项目的程度去整改工作量太大,不现实。
后面看了 Spring 注解的实现,然后结合找到反射实现资料,突想奇发尝试着用自定义注解+反射方式的去实现,事实证明这方法是可行的。
整体实现三步骤:
1.自定义注解
2.工具类方法实现反射
3.使用(测试)
1、自定义注解
import java.lang.annotation.*;
@Target({ElementType.FIELD,ElementType.TYPE}) // Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) // Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME 非常关键
@Documented
public @interface RelMapper {
//自定义属性
String value() default "";
String type() default ""; // value : status(标记属性值为Y/N的属性) / date(标记属性类型为时间)
}
自定义属性,大家可以根据自己项目中的需求增加不同的属性
2、工具类方法实现
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Date;
/**
* Java反射+注解实现Entity类与Dto类相互转换
* 情景:在项目中从Dao层到Service层数据传递中通过大量的get(),set()方法去一个一个的去拿值去赋值,导致代码篇幅过长,并且严重消耗开发时间;
* 尝试:现在大部分都是Entity类和Dto类的属性名相同的前提下,利用反射实现,太局限了,如果要改成同名,按目前项目的程度去整改工作量太大,不现实;
* 解决:看了Spring注解的实现,然后结合找到反射实现资料,突想奇发尝试着用自定义注解+反射方式的去实现,事实证明这方法是可行的。
* @Author: menghuan
* @Date: 2021/6/28 14:41
*/
public class RelationMapperUtils {
public static Object entryAndDtoMapper(Object entity, Object dto) throws Exception{
return EnAndDtoMapper(entity, dto,true);
}
public static Object entryAndDtoMapper(Object entity, Object dto,boolean enToDto) throws Exception{
return EnAndDtoMapper(entity, dto,false);
}
/**
* Entity and Dto Mapper
* @param entry
* @param dto
* @param enToDto
* ture : Entity To Dto (defult)
* false : Dto To Entry
* Rule:
* 实现相互转换前提: Dto field name(dto和entry的field name相同并且 类上有@RelMapper) 或 field的@RelMapper(value="Entity field name") 满足其一即可转换
* @return
* @throws Exception
*/
// last version
public static Object EnAndDtoMapper(Object entry, Object dto,boolean enToDto) throws Exception{
if(enToDto == true ? entry == null : dto == null){ return null;}
Class extends Object> entryclazz = entry.getClass(); //获取entity类
Class extends Object> dtoclazz = dto.getClass(); //获取dto类
boolean dtoExistAnno = dtoclazz.isAnnotationPresent(RelMapper.class); //判断类上面是否有自定义注解
Field[] dtofds = dtoclazz.getDeclaredFields(); //dto fields
Field [] entryfds = entryclazz.getDeclaredFields(); //entity fields
Method entrys[] = entryclazz.getDeclaredMethods(); //entity methods
Method dtos[] = dtoclazz.getDeclaredMethods(); //dto methods
String mName,fieldName,dtoFieldType=null,entFieldType=null,dtoMapName = null,dtoFieldName =null;Object value = null;
for(Method m : (enToDto ? dtos : entrys)) { //当 enToDto=true 此时是Entity转为Dto,遍历dto的属性
if((mName=m.getName()).startsWith("set")) { //只进set方法
fieldName = mName.toLowerCase().charAt(3) + mName.substring(4,mName.length()); //通过set方法获得dto的属性名
tohere:
for(Field fd: dtofds) {
fd.setAccessible(true); //setAccessible是启用和禁用访问安全检查的开关
if(fd.isAnnotationPresent(RelMapper.class)||dtoExistAnno){ //判断field上注解或类上面注解是否存在
//获取与Entity属性相匹配的映射值(两种情况:1.该field上注解的value值(Entity的field name 和Dto 的field name 不同) 2.该field本身(本身则是Entity的field name 和Dto 的field name 相同))
dtoMapName = fd.isAnnotationPresent(RelMapper.class) ? (fd.getAnnotation(RelMapper.class).value().toString().equals("")?fd.getName().toString():fd.getAnnotation(RelMapper.class).value().toString()):fd.getName().toString();
if(((enToDto ? fd.getName() : dtoMapName)).toString().equals(fieldName)) {
dtoFieldType = fd.getGenericType().toString().substring(fd.getGenericType().toString().lastIndexOf(".") + 1); // 获取dto属性的类型(如 private String field 结果 = String)
for(Field fe : entryfds) {
fe.setAccessible(true);
if(fe.getName().toString().equals(enToDto ? dtoMapName : fieldName) ) {//遍历Entity类的属性与dto属性注解中的value值匹配
entFieldType = fe.getGenericType().toString().substring(fe.getGenericType().toString().lastIndexOf(".") + 1); //获取Entity类属性类型
dtoFieldName = enToDto ? dtoMapName : fd.getName().toString();
break tohere;
}
}
}
}
}
if(dtoFieldName!= null && !dtoFieldName.equals("null")) {
for(Method md : (enToDto ? entrys : dtos)) {
if(md.getName().toUpperCase().equals("GET"+dtoFieldName.toUpperCase())){
dtoFieldName = null;
if(md.invoke(enToDto ? entry : dto) == null) { break;} //去空操作
//Entity类field 与Dto类field类型不一致通过TypeProcessor处理转换
value = (entFieldType.equals(dtoFieldType))? md.invoke(enToDto ? entry : dto) :TypeProcessor(entFieldType, dtoFieldType,md.invoke(enToDto ? entry : dto),enToDto ? true : false);
m.invoke(enToDto ? dto : entry, value); //得到field的值 通过invoke()赋值给要转换类的对应属性
value = null;
break;
}
}
}
}
}
return enToDto ? dto : entry;
}
// 类型转换处理
public static Object TypeProcessor(String entFieldType,String dtoFieldType, Object obj,boolean enToDto) {
if(entFieldType.equals(dtoFieldType)) return obj;
if(!entFieldType.equals(dtoFieldType)) {
switch(entFieldType) {
case "Date":
return (enToDto)?TypeConverter.dateToString((Date) obj):TypeConverter.stringToDate(obj.toString());
case "Timestamp":
return TypeConverter.timestampToTimestampString((Timestamp)obj);
case "Integer":
return (enToDto) ? obj.toString() : Integer.parseInt((String)obj) ;
}
}
return obj;
}
}
上面 EnAndDtoMapper() 方法的实现是 Entity 和 Dto 之间互相转换结合在一起,enToDto = true 表示的是 Entity 转 Dto 实现,false 则相反。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* TypeConverter 转换工具类
* @Author: menghuan
* @Date: 2021/6/30 11:13
*/
public class TypeConverter {
private static Pattern linePattern = Pattern.compile("_([a-z])");
private static Pattern humpPattern = Pattern.compile("\\B(\\p{Upper})(\\p{Lower}*)");
/**
* 将Date转换为String类型
* 提示:Object类中为我们提供了toString方法,然而该方法对Date类进行转换时,往往达不到我们想要的效果
* @param var 入参
* @return
*/
public static String dateToString(Date var){
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
return sf.format(var);
}
/**
* 将String转换为Date类型
* @param var 时间字符串
* @return
*/
public static Date stringToDate(String var){
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
try {
//使用SimpleDateFormat的parse()方法生成Date
Date date = sf.parse(var);
return date;
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}
/**
* 将Timestamp转换为指定格式的String
* @param var 时间戳
* @return
*/
public static String timestampToTimestampString(Timestamp var){
SimpleDateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");//定义日期显示格式
return df.format(var);
}
/**
* 将String转换为指定格式的Timestamp
* @param var 字符串,格式必须为:"yyyy-MM-dd HH:mm:ss"
* @return
*/
public static Timestamp timestampStringToTimestamp(String var){
Timestamp t_time = Timestamp.valueOf(var);
// String-->Timestamp:2010-08-08 06:06:06.0
// System.out.println("String-->Timestamp:"+t_time);
return t_time;
}
/**
* 实体对象转成Map
*
* @param obj 实体对象
* @return
*/
public static Map object2Map(Object obj) {
Map map = new HashMap<>();
if (obj == null) {
return map;
}
@SuppressWarnings("rawtypes")
Class clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
try {
for (Field field : fields) {
field.setAccessible(true);
map.put(field.getName(), field.get(obj));
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* Map转成实体对象
*
* @param map map实体对象包含属性
* @param clazz 实体对象类型
* @return
*/
public static Object map2Object(Map map, Class> clazz) {
if (map == null) {
return null;
}
Object obj = null;
try {
obj = clazz.newInstance();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
int mod = field.getModifiers();
if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
continue;
}
field.setAccessible(true);
field.set(obj, map.get(field.getName()));
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
/**
* 下划线转驼峰
*
* @param str
* @return
*/
public static String lineToHump(String str) {
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
/**
* 驼峰转下划线(Map)
*
* @param str
* @return
*/
public static String humpToLine(String str) {
Map map = JSONObject.parseObject(str);
Map newMap = Maps.newHashMap();
Iterator> it = map.entrySet().iterator();
while (it.hasNext()) {
StringBuffer sb = new StringBuffer();
Map.Entry entry = it.next();
String key = entry.getKey();
Matcher matcher = humpPattern.matcher(key);
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
newMap.put(sb.toString(), entry.getValue());
}
return JSON.toJSONString(newMap);
}
/**
* 驼峰转下划线(List)
*
* @param str
* @return
*/
@SuppressWarnings("all")
public static String humpToLineList(String str) {
List
3、如何使用
Entity 类 与 Dto 类对应
BeanUtils.copyProperties(iotHouseDTO,iotHouse);