SpringBoot 如何進行對象復制,老鳥們都這么玩的!

      網(wǎng)友投稿 1467 2025-04-02

      大家好,我是飄渺。


      今天帶來springboot老鳥系列的第四篇,來聊聊在日常開發(fā)中如何優(yōu)雅的實現(xiàn)對象復制。

      首先我們看看為什么需要對象復制?

      為什么需要對象復制

      如上,是我們平時開發(fā)中最常見的三層MVC架構模型,編輯操作時Controller層接收到前端傳來的DTO對象,在Service層需要將DTO轉換成DO,然后在數(shù)據(jù)庫中保存。查詢操作時Service層查詢到DO對象后需要將DO對象轉換成VO對象,然后通過Controller層返回給前端進行渲染。

      這中間會涉及到大量的對象轉換,很明顯我們不能直接使用getter/setter復制對象屬性,這看上去太low了。想象一下你業(yè)務邏輯中充斥著大量的getter&setter,代碼評審時老鳥們會如何笑話你?

      所以我們必須要找一個第三方工具來幫我們實現(xiàn)對象轉換。

      看到這里有同學可能會問,為什么不能前后端都統(tǒng)一使用DO對象呢?這樣就不存在對象轉換呀?

      設想一下如果我們不想定義 DTO 和 VO,直接將 DO 用到數(shù)據(jù)訪問層、服務層、控制層和外部訪問接口上。此時該表刪除或則修改一個字段,DO 必須同步修改,這種修改將會影響到各層,這并不符合高內(nèi)聚低耦合的原則。通過定義不同的 DTO 可以控制對不同系統(tǒng)暴露不同的屬性,通過屬性映射還可以實現(xiàn)具體的字段名稱的隱藏。不同業(yè)務使用不同的模型,當一個業(yè)務發(fā)生變更需要修改字段時,不需要考慮對其它業(yè)務的影響,如果使用同一個對象則可能因為 “不敢亂改” 而產(chǎn)生很多不優(yōu)雅的兼容性行為。

      對象復制工具類推薦

      對象復制的類庫工具有很多,除了常見的Apache的BeanUtils,spring的BeanUtils,Cglib BeanCopier,還有重量級組件MapStruct,Orika,Dozer,ModelMapper等。

      如果沒有特殊要求,這些工具類都可以直接使用,除了Apache的BeanUtils。原因在于Apache BeanUtils底層源碼為了追求完美,加了過多的包裝,使用了很多反射,做了很多校驗,所以導致性能較差,并在阿里巴巴開發(fā)手冊上強制規(guī)定避免使用 Apache BeanUtils。

      至于剩下的重量級組件,綜合考慮其性能還有使用的易用性,我這里更推薦使用Orika。Orika底層采用了javassist類庫生成Bean映射的字節(jié)碼,之后直接加載執(zhí)行生成的字節(jié)碼文件,在速度上比使用反射進行賦值會快很多。

      國外大神 baeldung 已經(jīng)對常見的組件性能進行過詳細測試,大家可以通過 https://www.baeldung.com/java-performance-mapping-frameworks 查看。

      Orika基本使用

      要使用Orika很簡單,只需要簡單四步:

      引入依賴

      ma.glasnost.orika orika-core 1.5.4

      1

      2

      3

      4

      5

      構造一個MapperFactory

      MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

      1

      注冊字段映射

      mapperFactory.classMap(SourceClass.class, TargetClass.class) .field("firstName", "givenName") .field("lastName", "sirName") .byDefault() .register();

      1

      2

      3

      4

      5

      當字段名在兩個實體不一致時可以通過.field()方法進行映射,如果字段名都一樣則可省略,byDefault()方法用于注冊名稱相同的屬性,如果不希望某個字段參與映射,可以使用exclude方法。

      進行映射

      MapperFacade mapper = mapperFactory.getMapperFacade(); SourceClass source = new SourceClass(); // set some field values ... // map the fields of 'source' onto a new instance of PersonDest TargetClass target = mapper.map(source, TargetClass.class);

      1

      2

      3

      4

      5

      6

      7

      經(jīng)過上面四步我們就完成了SourceClass到TargetClass的轉換。至于Orika的其他使用方法大家可以參考 http://orika-mapper.github.io/orika-docs/index.html

      看到這里,肯定有粉絲會說:你這推薦的啥玩意呀,這個Orika使用也不簡單呀,每次都要這先創(chuàng)建MapperFactory,建立字段映射關系,才能進行映射轉換。

      別急,我這里給你準備了一個工具類OrikaUtils,你可以通過文末github倉庫獲取。

      它提供了五個公共方法:

      分別對應:

      字段一致實體轉換

      字段不一致實體轉換(需要字段映射)

      字段一致集合轉換

      字段不一致集合轉換(需要字段映射)

      字段屬性轉換注冊

      接下來我們通過單元測試案例重點介紹此工具類的使用。

      Orika工具類使用文檔

      先準備兩個基礎實體類,Student,Teacher。

      @Data @AllArgsConstructor @NoArgsConstructor public class Student { private String id; private String name; private String email; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      @Data @AllArgsConstructor @NoArgsConstructor public class Teacher { private String id; private String name; private String emailAddress; }

      1

      2

      3

      4

      5

      6

      7

      8

      TC1,基礎實體映射

      /** * 只拷貝相同的屬性 */ @Test public void convertObject(){ Student student = new Student("1","javadaily","jianzh5@163.com"); Teacher teacher = OrikaUtils.convert(student, Teacher.class); System.out.println(teacher); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      輸出結果:

      Teacher(id=1, name=javadaily, emailAddress=null)

      1

      此時由于屬性名不一致,無法映射字段email。

      TC2,實體映射 - 字段轉換

      /** * 拷貝不同屬性 */ @Test public void convertRefObject(){ Student student = new Student("1","javadaily","jianzh5@163.com"); Map refMap = new HashMap<>(1); //map key 放置 源屬性,value 放置 目標屬性 refMap.put("email","emailAddress"); Teacher teacher = OrikaUtils.convert(student, Teacher.class, refMap); System.out.println(teacher); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      輸出結果:

      Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

      1

      此時由于對字段做了映射,可以將email映射到emailAddress。注意這里的refMap中key放置的是源實體的屬性,而value放置的是目標實體的屬性,不要弄反了。

      TC3,基礎集合映射

      /** * 只拷貝相同的屬性集合 */ @Test public void convertList(){ Student student1 = new Student("1","javadaily","jianzh5@163.com"); Student student2 = new Student("2","JAVA日知錄","jianzh5@xxx.com"); List studentList = Lists.newArrayList(student1,student2); List teacherList = OrikaUtils.convertList(studentList, Teacher.class); System.out.println(teacherList); }

      1

      2

      SpringBoot 如何進行對象復制,老鳥們都這么玩的!

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      輸出結果:

      [Teacher(id=1, name=javadaily, emailAddress=null), Teacher(id=2, name=JAVA日知錄, emailAddress=null)]

      1

      此時由于屬性名不一致,集合中無法映射字段email。

      TC4,集合映射 - 字段映射

      /** * 映射不同屬性的集合 */ @Test public void convertRefList(){ Student student1 = new Student("1","javadaily","jianzh5@163.com"); Student student2 = new Student("2","JAVA日知錄","jianzh5@xxx.com"); List studentList = Lists.newArrayList(student1,student2); Map refMap = new HashMap<>(2); //map key 放置 源屬性,value 放置 目標屬性 refMap.put("email","emailAddress"); List teacherList = OrikaUtils.convertList(studentList, Teacher.class,refMap); System.out.println(teacherList); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      輸出結果:

      [Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知錄, emailAddress=jianzh5@xxx.com)]

      1

      也可以通過這樣映射:

      Map refMap = new HashMap<>(2); refMap.put("email","emailAddress"); List teacherList = OrikaUtils.classMap(Student.class,Teacher.class,refMap) .mapAsList(studentList,Teacher.class);

      1

      2

      3

      4

      TC5,集合與實體映射

      有時候我們需要將集合數(shù)據(jù)映射到實體中,如Person類

      @Data public class Person { private List nameParts; }

      1

      2

      3

      4

      現(xiàn)在需要將Person類nameParts的值映射到Student中,可以這樣做

      /** * 數(shù)組和List的映射 */ @Test public void convertListObject(){ Person person = new Person(); person.setNameParts(Lists.newArrayList("1","javadaily","jianzh5@163.com")); Map refMap = new HashMap<>(2); //map key 放置 源屬性,value 放置 目標屬性 refMap.put("nameParts[0]","id"); refMap.put("nameParts[1]","name"); refMap.put("nameParts[2]","email"); Student student = OrikaUtils.convert(person, Student.class,refMap); System.out.println(student); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      輸出結果:

      Student(id=1, name=javadaily, email=jianzh5@163.com)

      1

      TC6,類類型映射

      有時候我們需要類類型對象映射,如BasicPerson類

      @Data public class BasicPerson { private Student student; }

      1

      2

      3

      4

      現(xiàn)在需要將BasicPerson映射到Teacher

      /** * 類類型映射 */ @Test public void convertClassObject(){ BasicPerson basicPerson = new BasicPerson(); Student student = new Student("1","javadaily","jianzh5@163.com"); basicPerson.setStudent(student); Map refMap = new HashMap<>(2); //map key 放置 源屬性,value 放置 目標屬性 refMap.put("student.id","id"); refMap.put("student.name","name"); refMap.put("student.email","emailAddress"); Teacher teacher = OrikaUtils.convert(basicPerson, Teacher.class,refMap); System.out.println(teacher); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      輸出結果:

      Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

      1

      TC7,多重映射

      有時候我們會遇到多重映射,如將StudentGrade映射到TeacherGrade

      @Data public class StudentGrade { private String studentGradeName; private List studentList; } @Data public class TeacherGrade { private String teacherGradeName; private List teacherList; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      這種場景稍微復雜,Student與Teacher的屬性有email字段不相同,需要做轉換映射;StudentGrade與TeacherGrade中的屬性也需要映射。

      /** * 一對多映射 */ @Test public void convertComplexObject(){ Student student1 = new Student("1","javadaily","jianzh5@163.com"); Student student2 = new Student("2","JAVA日知錄","jianzh5@xxx.com"); List studentList = Lists.newArrayList(student1,student2); StudentGrade studentGrade = new StudentGrade(); studentGrade.setStudentGradeName("碩士"); studentGrade.setStudentList(studentList); Map refMap1 = new HashMap<>(1); //map key 放置 源屬性,value 放置 目標屬性 refMap1.put("email","emailAddress"); OrikaUtils.register(Student.class,Teacher.class,refMap1); Map refMap2 = new HashMap<>(2); //map key 放置 源屬性,value 放置 目標屬性 refMap2.put("studentGradeName", "teacherGradeName"); refMap2.put("studentList", "teacherList"); TeacherGrade teacherGrade = OrikaUtils.convert(studentGrade,TeacherGrade.class,refMap2); System.out.println(teacherGrade); }

      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

      多重映射的場景需要根據(jù)情況調(diào)用OrikaUtils.register()注冊字段映射。

      輸出結果:

      TeacherGrade(teacherGradeName=碩士, teacherList=[Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知錄, emailAddress=jianzh5@xxx.com)])

      1

      TC8,MyBaits plus分頁映射

      如果你使用的是mybatis的分頁組件,可以這樣轉換

      public IPage selectPage(UserDTO userDTO, Integer pageNo, Integer pageSize) { Page page = new Page<>(pageNo, pageSize); LambdaQueryWrapper query = new LambdaQueryWrapper(); if (StringUtils.isNotBlank(userDTO.getName())) { query.like(User::getKindName,userDTO.getName()); } IPage pageList = page(page,query); // 實體轉換 SysKind轉化為SysKindDto Map refMap = new HashMap<>(3); refMap.put("kindName","name"); refMap.put("createBy","createUserName"); refMap.put("createTime","createDate"); return pageList.convert(item -> OrikaUtils.convert(item, UserDTO.class, refMap)); }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      小結

      在MVC架構中肯定少不了需要用到對象復制,屬性轉換的功能,借用Orika組件,可以很簡單實現(xiàn)這些功能。本文在Orika的基礎上封裝了工具類,進一步簡化了Orika的操作,希望對各位有所幫助。

      最后,我是飄渺Jam,一名寫代碼的架構師,做架構的程序員,期待您的轉發(fā)與關注,當然也可以添加我的個人微信 jianzh5,咱們一起聊技術!

      老鳥系列源碼已經(jīng)上傳至GitHub,需要的點擊下方名片,關注公眾號后回復關鍵字 0923獲取

      Spring boot

      版權聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權內(nèi)容。

      版權聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權內(nèi)容。

      上一篇:excel表格怎么自動填充公式(Excel公式自動填充)
      下一篇:統(tǒng)計報表產(chǎn)值填錯的原因(報表數(shù)據(jù)填錯了)
      相關文章
      亚洲欧洲日产国码无码久久99 | 亚洲色无码国产精品网站可下载| 日本亚洲视频在线| 中文字幕亚洲综合久久男男| 亚洲国产高清精品线久久| 一本色道久久综合亚洲精品蜜桃冫| 亚洲国产品综合人成综合网站| 亚洲视频小说图片| 久久青青草原亚洲av无码app| 亚洲国产精品免费视频| 亚洲电影一区二区三区| 亚洲精品免费视频| 亚洲天天在线日亚洲洲精| 亚洲日本在线看片| 亚洲美免无码中文字幕在线| 亚洲妇女水蜜桃av网网站| 亚洲乱码在线播放| 99亚偷拍自图区亚洲| 亚洲经典千人经典日产| 色欲aⅴ亚洲情无码AV蜜桃| 亚洲AV成人精品一区二区三区| 香蕉视频亚洲一级| 亚洲第一第二第三第四第五第六| 亚洲精品av无码喷奶水糖心| 无码天堂va亚洲va在线va| www国产亚洲精品久久久| 亚洲AV无码一区二区三区国产| 精品亚洲福利一区二区| 亚洲国产一区二区视频网站| 亚洲 无码 在线 专区| 亚洲一区二区三区无码影院| 亚洲欧洲日产国码无码网站| 亚洲AV电影院在线观看| 亚洲国产精品无码中文lv| 国产成人综合亚洲| 中文字幕亚洲日本岛国片| 亚洲AV一宅男色影视| 91精品国产亚洲爽啪在线影院 | 亚洲AV中文无码乱人伦| 精品亚洲视频在线观看| 久久亚洲AV永久无码精品|