SpringBoot实体类——VO/DTO/PO简单总结

本文介绍了Java后端开发中ViewObject(VO),DataTransferObject(DTO)和PersistantObject(PO)的概念和用途。VO用于前端展示,DTO用于服务层间数据传输,PO对应数据库表。文章详细阐述了它们之间的转换过程,包括使用Spring的BeanUtils和ModelMapper进行对象映射的示例,以及在用户注册和信息查询业务中的应用。

目录

1.概念叙述

2.类型转换

3.使用实例


1.概念叙述

VO:View Object,主要用于展示层。它的作用是把某个指定前端页面的所有数据封装起来。他的作用主要是减少传输数据量大小和保护数据库隐私数据(如用户密码、用户邮箱等相关信息)不外泄,同时保护数据库的结构不外泄。

DTO:Data Transfer Object,数据传输对象,用于展示层与服务层之间的数据传输对象。(注:实际开发中还存在BO,其作用和DTO类似,当业务逻辑不复杂时一般会被合并。)

PO:Persistant Object,持久化对象,和数据库形成映射关系。简单说PO就是每一个数据库中的数据表,一个字段对应PO中的一个变量。(也就是我们常用的Entities)

几者之间的关系如下图:

从前端页面中收到JSON格式数据,后端接口中将其封装为一个VO对象;接口接收到VO对象后将其转换为DTO对象,并调用业务类方法对其进行处理;然后处理为PO对象,调用Dao接口连接数据库进行数据访问(查询、插入、更新等)。

后端从数据库得到结果后,根据Dao接口将结果映射为PO对象,然后调用业务类方法将其转换为需要的DTO对象,再根据前端页面实际需求,转换为VO对象进行返回。

2.类型转换

上述过程中,VO/DTO/PO等实体类中字段常常会存在多数相同,根据业务需求少数不同。为避免频繁的set和get操作对其进行转换,spring为我们提供了多种方法。

(1)使用BeanUtils:(springframework包下)

UserDto user = new UserDto ();
BeanUtils.copyProperties(userInfo ,user ,new String[]{"birthday"});

上述代码中,意思是将左边UserInfo实体类(可以视为一个VO对象)和UserDto实体类类(可以视为一个DTO对象)中一样的值进行赋值user对象中,new String[]{""}中是跳过赋值的字段,该属性可以为空。

(2)使用BeanUtils:(Apache包下)

UserDto user = new UserDto ();
BeanUtils.copyProperties(user,userInfo);

上述代码中,意思是将右边UserInfo实体类(可以视为一个VO对象)和UserDto实体类类(可以视为一个DTO对象)中一样的值进行赋值user对象中。(和spring包下方法相反)

(3)使用modelMapper:

导入依赖:

        <dependency>
            <groupId>org.modelmapper</groupId>
            <artifactId>modelmapper</artifactId>
            <version>2.3.9</version>
        </dependency>

配置文件:

@Configuration
public class ModelMapperConfig {

    private Converter<Date, String> dateToStringConverter = new AbstractConverter<Date, String>() {
        @Override
        protected String convert(Date date) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            return date == null ? null : simpleDateFormat.format(date);
        }
    };

    @Bean
    public ModelMapper modelMapper() {
        ModelMapper modelMapper = new ModelMapper();

        // 官方配置说明: http://modelmapper.org/user-manual/configuration/
        // 完全匹配
        modelMapper.getConfiguration().setFullTypeMatchingRequired(true);

        // 匹配策略使用严格模式
        modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);

        modelMapper.addConverter(dateToStringConverter);

        configureUser(modelMapper);

        return modelMapper;
    }

    private void configureUser(ModelMapper modelMapper) {
        //将dto转为UserAccount实体类
        modelMapper.typeMap(UserInfor.class, UserAccount.class)
                .addMappings(mapper -> mapper.map(UserInfor::getUsername, UserAccount::setUsername))
                .addMappings(mapper -> mapper.map(UserInfor::getPassword, UserAccount::setPassword));

}

使用:

UserAccount account= modelMapper.map(userInfo,UserAccount.class);

上述代码,根据配置文件中configureUser方法中配置的字段,将两个实体类中的字段进行复制赋值。

3.使用实例

下面我们模拟一个业务:后端接口从前端中接收到用户注册数据,进行注册;前端调用查询接口,获取用户的数据。

首先,我们定义VO、DTO、PO对象:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo implements Serializable {
    //该实体类为封装好的前端传输页面VO对象
    @NotBlank(message = "用户名不能为空")
    private String Username;
    @NotBlank(message = "密码不能为空")
    private String Password;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO implements Serializable {
    //该实体类为业务处理DTO对象
    private int Account;
    private String Username;
    private String Password;
    private String Roles;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user_account")
public class UserAccount implements Serializable {
    //该实体类为用户账号表对应PO对象
    @TableId
    private int SerialNum;
    private int Account;
    private String Username;
    private String Password;
    private String Roles;
    private String status;
    private String registerDate;
}

然后,我们编写用户注册相关代码(基于springboot+mybatis plus),由于使用mybatis plus,此处省略mapper层代码,仅展示serviceImpl和controller中代码。

控制层:

    //用户注册
    @PostMapping("/register")
    public CommonResult<Object> userRegister(@RequestBody @Valid UserInfo user){
        int result = userService.userRegister(user);
        if(result!=0){
            return CommonResult.success(result);
        }else {
            return CommonResult.fail(result);
        }
    }

业务处理:

    @Override
    public int userRegister(UserInfo user) {
        if(accountMapper.selectCount(new QueryWrapper<UserAccount>().eq("username",user.getUsername()))!=0){
            log.info("用户名已存在");
            return 0;
        }
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(userDTO,user);
        //加密密码
        Md5Hash md5Hash = new Md5Hash(userDTO.getPassword(), userDTO.getUsername(),2);
        userDTO.setPassword(md5Hash.toString());
        //模拟生成账号
        userDTO.setAccount(12345678);
        //赋予权限
        userDTO.setRoles("user");
        return accountMapper.insert(userDTO);
    }

最终,我们可以往数据库中传入一条用户刚注册的账号数据,status和RegisterDate等属性取数据库设置好的默认值。

然后,我们再定义一个VO用于展示用户的信息:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserVo implements Serializable {
    //该VO用于展示用户信息,去除了用户密码等敏感信息
    private int serialNum;
    private int account;
    private String username;
}

进行用户信息的查询,获取所有的用户账号:

业务层:

    public List<UserAccount> getAllUser() {
        return accountMapper.selectList(null);
    }

控制层:

    @GetMapping("/get/all")
    public CommonResult<Object> getAllAccount() throws InvocationTargetException, IllegalAccessException {
        //查询所有数据
        List<UserAccount> userAccountList = userService.getAllUser();
        List<AdminInfo> resultList = new ArrayList<>();
        //封装为VO进行展示
        for(UserAccount u:userAccountList){
            UserVo userVo = new UserVo();
            BeanUtils.copyProperties(userVo,u);
            resultList.add(userVo);
        }
        //根据序列号进行排序,后注册的放在最前面
        resultList.sort(new Comparator<UserVo>() {
            @Override
            public int compare(UserVo o1, UserVo o2) {
                return o2.getSerialNum() - o1.getSerialNum();
            }
        });
        return new CommonResult<>(200,"账号列表如下:",resultList,resultList.size());
    }

然后我们就可查询得到所有的去除了敏感信息可用于展示的用户账号信息。

注:此处由于业务简单,没有将PO对象转换为DTO再转换为VO。

Java开发中,VO(View Object)、PO(Persistent Object)、DTO(Data Transfer Object)和QO(Query Object)是常见的实体类设计模式,它们各自有不同的职责与使用场景。当这些对象中包含敏感字段时(如用户密码、身份证号、手机号等),需要采取一定的策略进行处理,以确保数据安全。 ### VO(View Object) VO 通常用于向前端展示数据,应避免将敏感信息直接暴露给前端。可以采用以下方式: - **字段过滤**:在构建 VO 对象时,不包含敏感字段。 - **数据脱敏**:对某些必须展示但需隐藏部分内容的字段(如手机号、身份证号)进行脱敏处理,例如将 `13800138000` 显示为 `138****8000`。 - **权限控制**:根据用户角色动态决定是否填充敏感字段或对其进行脱敏处理。 ```java public class UserVO { private String username; private String maskedPhone; // Getter and Setter } ``` ### PO(Persistent Object) PO 是与数据库表结构一一对应的持久化对象,通常用于 ORM 框架中。对于敏感字段: - **加密存储**:在保存到数据库前,对敏感字段进行加密处理(如 AES 加密),读取时再解密。 - **字段映射配置**:通过注解或 XML 配置,指定敏感字段不在查询结果中返回,或者自动跳过序列化/反序列化过程。 - **使用 Hibernate 的 @Column(insertable = false, updatable = false)** 控制字段的插入和更新行为[^2]。 ```java @Entity public class UserPO { @Id private Long id; @Column(name = "password", insertable = false, updatable = false) private String password; // 其他字段及 getter/setter } ``` ### DTO(Data Transfer Object) DTO 主要用于跨层或跨服务的数据传输,在此过程中更应注重安全性: - **字段裁剪**:仅传输必要的字段,避免携带敏感信息。 - **脱敏转换**:在从 DO 或 PO 转换为 DTO 时,对敏感字段做脱敏处理。 - **使用 MapStruct 等工具进行定制化转换**:结合自定义方法实现字段处理逻辑。 ```java public class UserDTO { private String username; private String maskedEmail; // Getter and Setter } ``` ### QO(Query Object) QO 用于封装查询条件,特别是在复杂查询中非常有用。对于敏感字段: - **参数校验与脱敏**:如果查询条件涉及敏感字段(如手机号),应在接收请求后对输入内容进行校验与脱敏。 - **防止 SQL 注入**:使用预编译语句或框架内置机制,避免恶意输入导致的安全问题。 - **权限隔离**:根据用户权限限制可查询的字段范围,如普通用户无法按他人手机号查询。 ```java public class UserQuery { private String username; private String phone; public void setPhone(String phone) { this.phone = maskPhoneNumber(phone); } private String maskPhoneNumber(String phone) { if (phone == null || phone.length() < 7) return phone; return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); } // Getter and Setter } ``` ### 综合建议 - **统一脱敏工具类**:建立一个通用的脱敏工具类,供各层调用,保证脱敏规则一致性。 - **AOP 切面处理**:在数据返回前通过切面逻辑自动处理敏感字段。 - **配置中心管理敏感字段规则**:便于灵活调整脱敏策略,无需修改代码即可生效。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值