Java 中的 VO、DTO、PO、DO 是如何定义和互相转换的? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
devswork
V2EX    Java

Java 中的 VO、DTO、PO、DO 是如何定义和互相转换的?

  •  
  •   devswork 2022-04-28 14:54:20 +08:00 6473 次点击
    这是一个创建于 1263 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1.项目中 VO 、DTO 、PO 、DO 都是怎么定义(概念)?包括这些 O 在项目 package 路径里是怎么定义的( com.xxx.model.vo )? 2.这些领域对象都是如何转换的?手动 new set get 非常痛苦 3.针对查询参数传递到 mybatis ,是需要单独的写 xxxxParam 来传参呢? 4.若不写 VO ,是不是 swagger 里就不能显示响应格式、请求参数(body)了?

    39 条回复    2022-08-22 14:30:03 +08:00
    wolfie
        1
    wolfie  
       2022-04-28 14:57:19 +08:00
    mapstruct (快) 或者 BeanUtil (慢)
    TuringHero
        2
    TuringHero  
       2022-04-28 15:04:23 +08:00
    MapStruct 正解
    sinnosong1
        3
    sinnosong1  
       2022-04-28 15:05:18 +08:00
    sinnosong1
        4
    sinnosong1  
       2022-04-28 15:06:56 +08:00
    手动转换痛苦可以用用类似 C#中的"AutoMapper"的库,java 也有只是一般是个人开源项目。
    kytrun
        5
    kytrun  
       2022-04-28 15:33:25 +08:00
    推荐 IDEA 的插件 vo2dto ,减轻工作量: https://plugins.jetbrains.com/plugin/18262-vo2dto
    VeryZero
        6
    VeryZero  
       2022-04-28 15:37:21 +08:00
    将常用转换操作封装成静态方法写在对应的 POJO 里。

    比如:
    class A {
    public Long aa;

    public static B2A(B b){
    var a = new A();
    a.aa = B.bb;
    return a;
    }
    }
    sujin190
        7
    sujin190  
    &nbs;  2022-04-28 15:41:30 +08:00
    lombok 为啥不搞个这种支持。。用的地方这么多了,mapstruct 或者 BeanUtil 都不能完美支持静态类型检查,否则重构修改的时候不运行就不知道有问题
    aguesuka
        8
    aguesuka  
       2022-04-28 16:09:31 +08:00
    @sujin190 Java 是 Nominal type system, 如果完美支持静态类型检查就是 Structural type system 了, 而两个系统是冲突的.

    当然可以通过改 IDE 插件来做一个穷人版的, 有时间我写一个
    sujin190
        9
    sujin190  
       2022-04-28 16:18:19 +08:00
    @aguesuka #8 我的完美只是说比如 PO 改了字段类型或者删掉了某个字段,如果有 PO 转 DTO 的操作那么编译时就应该有提示,BeanUtil 这种运行时反射显然不行吧,不管 Java 是啥类型系统,我手写 getter setter 肯定是会报错毫无疑问的吧

    IDE 插件似乎想做运行时反射类型检查应该不那么容易做吧,否则就是 generate 了,似乎还是不如 lombok 这种方便吧
    jellywong
        10
    jellywong  
       2022-04-28 16:23:04 +08:00
    mapstruct
    wolfie
        11
    wolfie  
       2022-04-28 16:30:43 +08:00
    @sujin190 #9
    mapstruct 编译期类型不一致会抛异常。一般类型会帮转换。
    aguesuka
        12
    aguesuka  
       2022-04-28 16:32:11 +08:00
    @sujin190 Structural type 是像 ts 那样, 是编译时不是运行时的. 如果 DO 和 DTO 字段完全一样, 那么它们就是一个类型, 如果少数字段不一样, 我们可以用解构语法, 并且是静态安全的.
    Oktfolio
        13
    Oktfolio  
       2022-04-28 16:37:17 +08:00
    AutoMapper 类似的库有 orika-mapper 和 DozerMapper ,但是好像都不如 AutoMapper 好用

    反正我不喜欢 MapStruct ,就像不喜欢 Lombok 一样
    sujin190
        14
    sujin190  
       2022-04-28 16:50:13 +08:00
    @wolfie #11 这货烦人的就是不能像 Lombok 加个注解就搞定,interface 也不想加,否则就用 idea 的 generate 了
    GBdG6clg2Jy17ua5
        15
    GBdG6clg2Jy17ua5  
       2022-04-28 16:58:06 +08:00
    1.项目中和数据库一致的,定义为 dto ,如果需要特别扩展属性的,加一个 vo 。其他什么 po,do 乱七八糟的一概不用。
    2.路径按照功能模块,房子啊 com.xxx.model
    3.mapstruct
    4.dto ,vo 混用。别介意。
    nothingistrue
        16
    nothingistrue  
       2022-04-28 17:11:33 +08:00   1
    这个已经过时了,除非你是在改当前项目,不建议再去深究了。

    现在,不管是 Hibernate 还是 Mybatis plus ,不管是 DDD 还是非 DDD ,Entity 都是一个特殊的对象类型,这个很好区分,他是跟数据库的表映射或者绑定的(如果是 DDD ,它还有行为方法)。

    DTO 在是一个重对象,它还会一直用下去,但是很少会使用。它的区分也很简单,它是一个重对象,自带数据转换逻辑,并且通常跟工厂一起使用。只有 Getter/Setter 的轻对象,不会是 DTO 的,国内有些人把上下层之间传输的参数一律叫做 DTO ,这是很大的误区。

    然后剩下的,VO 、PO 、DO 什么的,它们原本的定义是跟层绑定的,一个层使用一种 O ,禁止跨层使用(层与层要额外通过 DTO 来隔离,比如 VO 经 DTO 转换成 DO )或者只允许上层使用下层的。这些已经死翘翘了。前面已经说了,DTO 很重,没有人会采用“禁止跨层使用”的方式,都是采用“只允许上层使用下层”方式。然后,既然允许上层调用下层了,那为什么不直接调用 Entity 呢,所以最后全部都用 Entity 了。

    简单来说,除了 Entity 、DTO ,剩下的本质上都是 Data ,为了层解耦才定义了那么多 O 。随着垂直分层模式的崩溃,这些 O 也崩溃了。

    至于楼主的其他问题。2 ,如果你要负责任的话,那就必须手动转换,再痛苦也要转,业务太多变了,这玩意工具的作用很有限,当然有缓解的手段,对于一些纯内部使用的类,你可以考虑 lombok 的 chain 或 fluent 模式。3 ,是,专项专用,但是你可以通过合理规划查询 SQL ,使得多个 SQL 公用一个参数类。4.如果你要负责任的话,那必须给 Swagger ,或者说 Web 这一层,定义专门的数据对象( Swagger 叫做 model ,内部可定义为 value object VO ,也可以就叫做 Data )。
    pocketz
        17
    pocketz  
       2022-04-28 18:37:46 +08:00
    itechify
        18
    itechify  
    PRO
       2022-04-28 19:05:03 +08:00
    @sujin190 #7 Mapstruct 编译的时候就生成代码的,会抛出异常
    qW7bo2FbzbC0
        19
    qW7bo2FbzbC0  
       2022-04-28 19:13:15 +08:00
    @nothingistrue #16 只有 getter setter 的后缀是什么? RD => Record?
    lessMonologue
        20
    lessMonologue  
       2022-04-28 19:17:02 +08:00
    @nothingistrue 大佬请问有没有什么比较标准的开源项目可以参考一下的
    MakHoCheung
        21
    MakHoCheung  
       2022-04-28 20:04:57 +08:00
    看了下评论,能否通俗点?
    1. DAO 返回给 Service 的是只返回 Entity ( PO )吗,如果涉及到连表的话是不是要返回 DTO ?
    2. DAO 返回的 Entity 需要在 Service 转成 DTO 然后再 Controller 层转成 VO 返回给前端吗,如果三者内容一致呢,是不是有多余重复的复制操作了


    C#、Go 开发 Web 有这么多规范吗
    KingOfUSA
        22
    KingOfUSA  
       2022-04-28 20:32:24 +08:00
    推荐这个库 https://github.com/ksprider/Surgical 至少在 controller 层可以无需转换成 vo ,也能将所需要的字段序列化掉,而不用新建一层 vo ,主要是这个 vo 复用性为 0 呐,
    aragakiyuii
        23
    aragakiyuii  
       2022-04-28 21:41:06 +08:00
    基本都是贫血模型搞这么多概念干嘛。。。需要去区分字段的时候定义一个新的就好了
    Leviathann
        24
    Leviathann  
       2022-04-28 21:47:20 +08:00
    一条垂直链路下来搞那么多纯属吃饱了撑的
    我们的项目只有 entity aggregate 输出输入专用对象 三种,如果算上部分字段查询结果的对象那就是四种
    逻辑都放在 aggregate 里
    leeg810312
        25
    leeg810312  
       2022-04-28 22:03:35 +08:00
    我同时用.net 和 Java 开发,所以行业内 2 个平台有各自的设计偏好我都有了解,现在实践中发现只用 2 种数据模型就足够了,Entity 用于数据表映射和 DTO 数据传输,太多模型分类都是过度设计,徒增无用的复杂性。ORM 获取数据都是 Entity ,DTO 顾名思义数据传输对象,那么不管转给中间服务层或是控制器都是 DTO ,不再分出其他的概念,不像楼上有人说会用得少。DTO 类的设计原则是根据业务需要,裁剪、增加、拼装 Entity 的属性,甚至可以增加一些辅助的方法,用平台常用的对象映射工具 AutoMapper/MapStruct 等进行 Entity 和 DTO 之间的转换。
    twing37
        26
    twing37  
       2022-04-28 22:50:32 +08:00
    1. VO 如果你写过脚本语言,如 php, 混在 V 层的模板对象就是 VO,
    2. DTO 前后端分类的,接口对象.
    > 注 1: 1 与 2 的区别在于, 举个例子, 如 性别字段, vo 里面是男, dto 里面是 1,
    3. 需要. 比如, http/grpc 的 json 与 proto --> service(use case)的逻辑层对象,
    4. 参见 1 与 2 的区别.
    > 注 2: 在足够简单的项目中,确定架构不会改变的情况下,请忽略这些.不管是 clean arch 还是 ddd 的六边形
    都不如梭哈. 我在之前的类似答案中也写过,在这个前提下,不如面条代码写来的更好.比如在 repo 下,只是简单的 curd,
    完全可以嵌入整个 store 逻辑,因为该逻辑很难再去复用.
    hingbong
        27
    hingbong  
       2022-04-29 00:07:54 +08:00 via Android
    我用 GitHub copilot 生成转换代码
    xuanbg
        28
    xuanbg  
       2022-04-29 06:59:11 +08:00
    我的做法是 DTO 用来接收数据,VO 返回数据。然后它们相同的字段从一个不带 XO 的同名类继承。相互转换很简单啊,就是 A 序列化后再反序列化成 B 就行了呀。效率虽低,但基本没有影响。谁又会在意一个接口的响应时间慢了几个微秒呢。
    MonkeyJon
        29
    MonkeyJon  
       2022-04-29 08:40:38 +08:00
    自己写个装换器或者 MapStruct
    securityCoding
        30
    securityCoding  
       2022-04-29 09:14:38 +08:00 via Android   1
    这套东西扔了吧,一群学院派搞出来的高大上概念
    nothingistrue
        31
    nothingistrue  
       2022-04-29 09:27:19 +08:00
    @hjahgdthab750 #19 后缀是由架构或规范决定的,没有特定规则。光凭“只有 getter setter ”这一个特点,是不知道这个类是干啥的,还要结合其他特点才能确切直到这个类是干啥的,之后才能命名。

    @lessMonologue #20 没有。就算有也不具备参考意义,因为这本来就是规范不是规则,是随团队变化的。

    @MakHoCheung #21 再严格一点,上下层之间不管调用还是返回都只能是 DTO ,DTO 负责对象的转换。例:Service 利用 DTO 的静态方法或者工厂方法将 BO 扔进 DTO ,再用这个 DTO 作为调用 DAO 的参数;反过来就是 DAO 将 Entity 利用工厂方法将扔进 DTO ,再返回 DTO 。这三者内容不可能一致的,Entity 是 ORM 的托管对象会带有 ORM 的信息,DTO 要负责数据转换会带有业务逻辑,BO 为 Service 服务会有业务逻辑的中间数据。然而,这随不是重复操作,但是属于杀鸡用牛刀的操作。

    @leeg810312 #25 你这个好,用 DTO 把数据转换和数据对象都给包了,就只剩下 Entity 和 DTO 两种,没有纯数据的 Data 了。
    notwaste
        32
    notwaste  
       2022-04-29 10:05:30 +08:00
    一路看下来都是经验之谈主观看法,那么我还是坚持我的想法跟着自身项目走就可以,本身就是为了规范而存在的
    LLaMA2
        33
    LLaMA2  
       2022-04-29 14:33:05 +08:00
    我一直想问各位大佬,mapstruct 中转换树形数据,不确定层级的嵌套数据有什么好办法,我用的时候每天被折磨的很难过,后来我用 TS 写代码,typeorm 就没有这个苦恼了
    Dlin
        34
    Dlin  
       2022-04-29 14:48:18 +08:00
    这些概念,往往都只是个概念,实践起来麻烦琐碎。
    issakchill
        35
    issakchill  
       2022-04-29 15:20:50 +08:00
    @ye4tar #33
    issakchill
        36
    issakchill  
       2022-04-29 15:23:59 +08:00
    @issakchill 错手回复了空内容.. 如果是树形转树形 mapstruct 好像是自动递归的
    cco
        37
    cco  
       2022-04-29 16:46:08 +08:00
    VO: 返回给前端,或者响应给请求者的对象。
    DTO: 请求参数、数据库查询列映射 负责数据传输。
    Entity: 和数据库表一一对应的实体对象。
    一般用这三个。这东西和接口与实现分包放一样,要不要都可以。对于数据库查询出来的数据,直接响应回去,那没必要 DTO (或者 Entity ) -> BO(可以理解为还是 DTO) -> VO ,直接返回即可。

    至于转换关系。mapstruct 、BeanUtil 都可以,不管什么都不可避免要手动加点进去。如果你查出来的列名和你要响应的字段都不一致呢?这些类库以及 idea 的插件也只是方便而已,不可能实现一键转换。除非你转换前和转换后几乎是一模一样的。
    kahlkn
        38
    kahlkn  
       2022-05-11 13:15:27 +08:00
    @sujin190 字段删掉的话,bean 就不 copy 字段,至于改字段类型,我是做了转换工具去转换得,总体上还是保留 不报错。 但是类型不一致是可以报错的,只是这些工具在实现的时候的考量可能是不报错。

    反射就能做到,用反射、Cglib 都封装过 bean copy 。
    generalfu
        39
    generalfu  
       2022-08-22 14:30:03 +08:00
    @kytrun 赞的很,好东西!
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2739 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 07:38 PVG 15:38 LAX 00:38 JFK 03:38
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86