前面的文章曾经提及过,我们可以使用 BeanUtils.copyProperties 进行对象的拷贝,可以用于 DO、DTO 等之间的转换,但是要注意它只能用于浅拷贝。所以如果你的对象是层层嵌套型,或者有数组 List 等等复杂的类型,其实 copyProperties 就很掣肘了。而且高并发下它会有性能问题,可能还没有 get set 效率高。具体原因后面再补充。我们回归正题。

下面介绍一种名为 Orika 的小框架,可以高效的实现深拷贝。

1. 准备

两个 DO 对象,即从数据库查询出的实体对象,Person 类,其中包含了几个常见类型的成员变量。

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @Builder
  5. public class Person implements Serializable {
  6. private String name;
  7. private Integer age;
  8. private Student student;
  9. private List<String> phoneList;
  10. private Integer gender;
  11. /**
  12. * Json 字符串
  13. */
  14. private String dateInfoStr;
  15. }
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class Student implements Serializable {
  5. private String schoolName;
  6. private Integer classId;
  7. }

下面是 Person 的 DTO 对象,其中的字段与 Person 存在对应,但是表达形式和名称可能不是很一致,例如 上面的 age 这里叫做 myAge,Integer 类型的 gender 性别,变成了一个枚举类,dateInfoStr 这个 json 字符串也变成了 dateInfo 对象。

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @Builder
  5. public class PersonDTO implements Serializable {
  6. private String name;
  7. private Integer myAge;
  8. private Student student;
  9. private List<String> phoneList;
  10. private GenderEnum gender;
  11. private DateInfo dateInfo;
  12. }
  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. public class StudentDTO implements Serializable {
  5. private String schoolName;
  6. private Integer classId;
  7. }
  1. public enum GenderEnum {
  2. MALE(0, "男"),
  3. FEMALE(1, "女");
  4. private Integer code;
  5. private String desc;
  6. GenderEnum(Integer code, String desc) {
  7. this.code = code;
  8. this.desc = desc;
  9. }
  10. public Integer getCode() {
  11. return code;
  12. }
  13. public String getDesc() {
  14. return desc;
  15. }
  16. public static GenderEnum getValue(Integer code){
  17. GenderEnum result = null;
  18. GenderEnum[] values = GenderEnum.values();
  19. for(GenderEnum genderEnum : values){
  20. if(genderEnum.code.equals(code)){
  21. result = genderEnum;
  22. }
  23. }
  24. return result;
  25. }
  26. }

我们要做的就是把 Person 转为 PersonDTO 对象,即实现一次深拷贝。

2. 实现

直接贴出例子

  • personList 就是你从数据库查询出的数据集合,类型是 Person,在静态代码块中准备数据。
  • 在主函数中开始遍历处理集合
  • 如果有一些特殊的匹配,需要自己处理下,例如下面注释中内容,如果都是比较基本的对象,而且名字都一一对应,其实不需要这么复杂。

    1. public class CopyTest {
    2. /**
    3. * 拷贝前的集合
    4. */
    5. private static List<Person> personList;
    6. static {
    7. // 数据准备
    8. Person.PersonBuilder builder = Person.builder()
    9. .name("张三")
    10. .age(18)
    11. .student(new Student("理想大学", 123))
    12. .phoneList(Collections.singletonList("13666666666"))
    13. .gender(0)
    14. .dateInfoStr("{\"uid\": \"1023079620966365582\", \n \"username\": \"ideal-20\" \n }");
    15. personList = new ArrayList<>();
    16. personList.add(builder.build());
    17. }
    18. public static void main(String[] args) {
    19. personList.forEach(personItem -> {
    20. MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
    21. // 处理 gender 这个字段 Integer 和 枚举类型之间的转换
    22. mapperFactory.
    23. getConverterFactory()
    24. .registerConverter("genderEnumConvert", new BidirectionalConverter<GenderEnum, Integer>() {
    25. @Override
    26. public Integer convertTo(GenderEnum genderEnum, Type<Integer> type, MappingContext mappingContext) {
    27. return genderEnum.getCode();
    28. }
    29. @Override
    30. public GenderEnum convertFrom(Integer integer, Type<GenderEnum> type, MappingContext mappingContext) {
    31. return GenderEnum.getValue(integer);
    32. }
    33. });
    34. // 处理 dateInfo 这个字段 json字符串 和 对象之间的转换
    35. mapperFactory
    36. .getConverterFactory()
    37. .registerConverter("dataInfoConvert", new JsonConfigConvert<DateInfo>());
    38. // 其余配置
    39. mapperFactory
    40. .classMap(Person.class, PersonDTO.class)
    41. .field("age", "myAge")
    42. .fieldMap("gender", "gender").converter("genderEnumConvert").add()
    43. .fieldMap("dateInfoStr", "dateInfo").converter("dataInfoConvert").add()
    44. .byDefault()
    45. .mapNulls(false)
    46. .register();
    47. // 转换
    48. MapperFacade mapperFacade = mapperFactory.getMapperFacade();
    49. PersonDTO personDTO = mapperFacade.map(personItem, PersonDTO.class);
    50. System.out.println("DO -> DTO:" + personDTO);
    51. System.out.println(personDTO.getGender().getCode() + "---" + personDTO.getGender().getDesc());
    52. });
    53. }
    54. public static class JsonConfigConvert<T> extends BidirectionalConverter<T, String> {
    55. @Override
    56. public String convertTo(T source, Type<String> destinationType, MappingContext mappingContext) {
    57. return JSON.toJSONString(source);
    58. }
    59. @Override
    60. public T convertFrom(String source, Type<T> destinationType, MappingContext mappingContext) {
    61. return JSON.parseObject(source, destinationType.getRawType());
    62. }
    63. }
    64. }

    运行结果:

    1. DO -> DTOPersonDTO(name=张三, myAge=18, student=Student(schoolName=理想大学, classId=123), phoneList=[13666666666], gender=MALE, dateInfo=DateInfo(uid=1023079620966365582, username=ideal-20))
    2. 0---男