DTO、VO、POJO与实体类使用方案(结合Mapper.xml)

结合MyBatis的Mapper.xml文件,展示完整的层级数据流转和数据库操作。

1. 实体类优化(Entity)

// User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long userId;
    
    @NotBlank
    private String username;
    
    @NotBlank
    private String password;
    
    private String email;
    private String phone;
    private Date createTime;
    
    // 非数据库字段,用于关联查询
    @TableField(exist = false)
    private List<Role> roles;
}

// Role.java
@Data
@TableName("sys_role")
public class Role {
    @TableId(type = IdType.AUTO)
    private Long roleId;
    private String roleName;
    private String roleDesc;
}

2. Mapper接口与XML配置

UserMapper.java

@Mapper
public interface UserMapper {
    // 插入用户并返回主键
    int insertUser(User user);
    
    // 根据ID查询用户(包含角色信息)
    User selectUserWithRoles(@Param("userId") Long userId);
    
    // 分页查询用户
    List<User> selectUserList(UserQueryDTO queryDTO);
    
    // 批量插入用户角色关系
    int batchInsertUserRoles(@Param("list") List<UserRole> userRoles);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.mapper.UserMapper">

    <!-- 基本结果映射 -->
    <resultMap id="BaseUserMap" type="com.example.entity.User">
        <id column="user_id" property="userId"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="email" property="email"/>
        <result column="phone" property="phone"/>
        <result column="create_time" property="createTime"/>
    </resultMap>

    <!-- 包含角色信息的用户映射 -->
    <resultMap id="UserWithRolesMap" type="com.example.entity.User" extends="BaseUserMap">
        <collection property="roles" ofType="com.example.entity.Role">
            <id column="role_id" property="roleId"/>
            <result column="role_name" property="roleName"/>
            <result column="role_desc" property="roleDesc"/>
        </collection>
    </resultMap>

    <!-- 插入用户 -->
    <insert id="insertUser" useGeneratedKeys="true" keyProperty="userId">
        INSERT INTO sys_user (username, password, email, phone)
        VALUES (#{username}, #{password}, #{email}, #{phone})
    </insert>

    <!-- 查询用户及其角色 -->
    <select id="selectUserWithRoles" resultMap="UserWithRolesMap">
        SELECT 
            u.*, 
            r.role_id, r.role_name, r.role_desc
        FROM 
            sys_user u
        LEFT JOIN sys_user_role ur ON u.user_id = ur.user_id
        LEFT JOIN sys_role r ON ur.role_id = r.role_id
        WHERE 
            u.user_id = #{userId}
    </select>

    <!-- 动态查询用户列表 -->
    <select id="selectUserList" resultMap="BaseUserMap">
        SELECT * FROM sys_user
        <where>
            <if test="username != null and username != ''">
                AND username LIKE CONCAT('%', #{username}, '%')
            </if>
            <if test="email != null and email != ''">
                AND email = #{email}
            </if>
            <if test="phone != null and phone != ''">
                AND phone = #{phone}
            </if>
            <if test="createTimeStart != null">
                AND create_time >= #{createTimeStart}
            </if>
            <if test="createTimeEnd != null">
                AND create_time &lt;= #{createTimeEnd}
            </if>
        </where>
        ORDER BY create_time DESC
    </select>

    <!-- 批量插入用户角色关系 -->
    <insert id="batchInsertUserRoles">
        INSERT INTO sys_user_role (user_id, role_id)
        VALUES
        <foreach collection="list" item="item" separator=",">
            (#{item.userId}, #{item.roleId})
        </foreach>
    </insert>
</mapper>

3. 服务层优化实现

@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
    
    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private final PasswordEncoder passwordEncoder;
    
    @Override
    @Transactional
    public UserVO createUser(UserDTO userDTO) {
        // DTO转Entity
        User user = new User();
        BeanUtils.copyProperties(userDTO, user);
        
        // 密码加密
        user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        
        // 保存用户
        userMapper.insertUser(user);
        
        // 保存用户角色关系
        if (!CollectionUtils.isEmpty(userDTO.getRoleIds())) {
            List<UserRole> userRoles = userDTO.getRoleIds().stream()
                .map(roleId -> new UserRole(user.getUserId(), roleId))
                .collect(Collectors.toList());
            userMapper.batchInsertUserRoles(userRoles);
        }
        
        // 返回完整的用户信息
        return getUserVO(user.getUserId());
    }
    
    @Override
    public PageVO<UserVO> getUsers(UserQueryDTO queryDTO) {
        // 设置分页参数
        PageHelper.startPage(queryDTO.getPageNum(), queryDTO.getPageSize());
        
        // 查询用户列表
        List<User> users = userMapper.selectUserList(queryDTO);
        PageInfo<User> pageInfo = new PageInfo<>(users);
        
        // 转换为VO列表
        List<UserVO> userVOs = users.stream()
            .map(user -> UserVO.fromEntity(user, getRolesByUserId(user.getUserId())))
            .collect(Collectors.toList());
        
        // 构建分页VO
        return new PageVO<>(
            pageInfo.getTotal(),
            pageInfo.getPageNum(),
            pageInfo.getPageSize(),
            userVOs
        );
    }
    
    @Override
    public UserVO getUserVO(Long userId) {
        User user = userMapper.selectUserWithRoles(userId);
        return UserVO.fromEntity(user, user.getRoles());
    }
    
    private List<Role> getRolesByUserId(Long userId) {
        return roleMapper.selectByUserId(userId);
    }
}

4. DTO/VO优化设计

UserDTO.java

@Data
public class UserDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 4, max = 20)
    private String username;
    
    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20)
    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d).*$", 
             message = "密码必须包含字母和数字")
    private String password;
    
    @Email
    private String email;
    
    @Pattern(regexp = "^1[3-9]\\d{9}$")
    private String phone;
    
    @NotEmpty(message = "至少分配一个角色")
    private List<Long> roleIds;
    
    // 自定义转换方法
    public User toEntity() {
        User user = new User();
        BeanUtils.copyProperties(this, user);
        return user;
    }
}

UserVO.java

@Data
@Accessors(chain = true)
public class UserVO {
    private Long userId;
    private String username;
    private String email;
    private String phone;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    
    private List<RoleVO> roles;
    
    // 静态工厂方法
    public static UserVO fromEntity(User user, List<Role> roles) {
        return new UserVO()
            .setUserId(user.getUserId())
            .setUsername(user.getUsername())
            .setEmail(user.getEmail())
            .setPhone(user.getPhone())
            .setCreateTime(user.getCreateTime())
            .setRoles(roles.stream()
                .map(RoleVO::fromEntity)
                .collect(Collectors.toList()));
    }
}

PageVO.java (通用分页VO)

@Data
@AllArgsConstructor
public class PageVO<T> {
    private Long total;
    private Integer pageNum;
    private Integer pageSize;
    private List<T> list;
    
    // 计算总页数
    public Integer getPages() {
        if (pageSize == 0) return 0;
        return (int) Math.ceil((double) total / pageSize);
    }
}

5. 控制器层优化

@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    
    private final UserService userService;
    
    @PostMapping
    public ResponseEntity<Result<UserVO>> createUser(
            @Valid @RequestBody UserDTO userDTO) {
        UserVO userVO = userService.createUser(userDTO);
        return ResponseEntity.ok(Result.success(userVO));
    }
    
    @GetMapping
    public ResponseEntity<Result<PageVO<UserVO>>> getUsers(
            @Valid UserQueryDTO queryDTO) {
        PageVO<UserVO> page = userService.getUsers(queryDTO);
        return ResponseEntity.ok(Result.success(page));
    }
    
    @GetMapping("/{userId}")
    public ResponseEntity<Result<UserVO>> getUser(
            @PathVariable Long userId) {
        UserVO userVO = userService.getUserVO(userId);
        return ResponseEntity.ok(Result.success(userVO));
    }
}

// 通用返回结果
@Data
@AllArgsConstructor
class Result<T> {
    private int code;
    private String message;
    private T data;
    
    public static <T> Result<T> success(T data) {
        return new Result<>(200, "success", data);
    }
}

6. 优化后的数据流转流程

  1. 前端请求Controller(接收DTO并校验)
  2. ControllerService(传递DTO)
  3. Service
    • 将DTO转换为Entity
    • 调用Mapper进行数据库操作
    • 将查询结果Entity转换为VO
  4. ServiceController(返回VO)
  5. Controller前端(返回VO数据)

7. 关键优化点

  1. Mapper.xml优化

    • 使用resultMap实现复杂结果映射
    • 动态SQL处理各种查询条件
    • 批量操作提高性能
  2. 对象转换优化

    • 在DTO/VO中定义转换方法
    • 使用链式调用简化代码
    • 静态工厂方法提高可读性
  3. 分页处理

    • 使用PageHelper实现物理分页
    • 统一分页返回结构
  4. 验证增强

    • 在DTO中使用更精细的验证注解
    • 密码复杂度验证
  5. 性能优化

    • 关联查询减少数据库访问次数
    • 批量插入提高效率

这种结构清晰地区分了各层职责,使代码更易维护和扩展,同时保证了良好的性能。

在 Java 或其他面向对象的编程语言中,尤其是在分层架构(如 MVC、DDD)系统中,**DTOVOPOJO、Entity** 是常见的实体类,它们分别用于不同的层次和场景。由于职责不同,这些对象之间经常需要进行数据转换。 --- ### ✅ 各种“对象”的定义用途 | 缩写 | 全称 | 中文含义 | 使用场景 | |------|------|--------|---------| | **POJO** | Plain Old Java Object | 普通 Java 对象 | 任意简单 Java Bean,无限制 | | **Entity** | 实体类 | 数据库表映射的对象 | 持久层(DAO/Repository),通常配合 JPA/Hibernate | | **DTO** | Data Transfer Object | 数据传输对象 | 跨层或跨服务传递数据(如 Controller 层输出) | | **VO** | Value Object / View Object | 值对象 / 视图对象 | 返回给前端的数据结构(可组合、脱敏、格式化) | > ⚠️ 注意:VO 在 DDD 中是“值对象”(不可变、无 ID),但在 Web 开发中常被当作 View Object 使用。 --- ## 🔄 实体之间的转换方式 ### 示例背景: 假设有一个用户管理系统: ```java // 1. Entity - 对应数据库 user 表 @Entity @Table(name = "user") public class UserEntity { @Id private Long id; private String username; private String password; // 敏感字段! private LocalDateTime createTime; // getters and setters... } // 2. DTO - 用于外部接口传输 public class UserDTO { private Long id; private String username; private LocalDateTime createTime; // getters and setters... } // 3. VO - 返回给前端的视图对象(可能包含额外信息) public class UserVO { private Long id; private String userName; private String createTimeFormatted; // 格式化时间字符串 // getters and setters... } // 4. POJO - 泛指上面所有类都可以称为 POJO(只要不是框架特殊类) ``` --- ## 🔧 转换方法一:手动 set/get(最安全,性能好) ```java public UserDTO toDTO(UserEntity entity) { if (entity == null) return null; UserDTO dto = new UserDTO(); dto.setId(entity.getId()); dto.setUsername(entity.getUsername()); dto.setCreateTime(entity.getCreateTime()); return dto; } public UserVO toVO(UserDTO dto) { UserVO vo = new UserVO(); vo.setId(dto.getId()); vo.setUserName(dto.getUsername()); vo.setCreateTimeFormatted( dto.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")) ); return vo; } ``` ✅ 优点:精确控制字段、类型转换、安全性高 ❌ 缺点:代码冗长,重复劳动 --- ## 🔧 转换方法二:使用 MapStruct(推荐!) [MapStruct](https://mapstruct.org/) 是一个注解处理器,在编译期生成类型安全的映射代码。 ### 添加依赖(Maven) ```xml <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.2.Final</version> </dependency> ``` 启用注解处理(JDK 8+): ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.2.Final</version> </path> </annotationProcessorPaths> </configuration> </plugin> ``` ### 定义 Mapper 接口 ```java @Mapper public interface UserConvertMapper { UserConvertMapper INSTANCE = Mappers.getMapper(UserConvertMapper.class); @Mapping(source = "username", target = "userName") @Mapping(target = "createTimeFormatted", expression = "java( entity.getCreateTime() != null ? entity.getCreateTime().format(java.time.format.DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm\")) : null )" ) UserVO entityToVO(UserEntity entity); // 只复制公共字段(忽略 password) @Mapping(target = "password", ignore = true) UserDTO entityToDTO(UserEntity entity); UserEntity dtoToEntity(UserDTO dto); } ``` ### 使用示例 ```java UserEntity entity = userRepository.findById(1L); UserDTO dto = UserConvertMapper.INSTANCE.entityToDTO(entity); UserVO vo = UserConvertMapper.INSTANCE.entityToVO(entity); ``` ✅ 优点: - 编译期生成代码,无反射开销,性能极高 - 类型安全,IDE 支持自动跳转 - 支持复杂表达式、自定义转换逻辑 --- ## 🔧 转换方法三:使用 ModelMapper(基于反射) 轻量级自动映射工具,适合小型项目。 ### Maven 依赖 ```xml <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmapper</artifactId> <version>3.1.0</version> </dependency> ``` ### 使用 ```java ModelMapper modelMapper = new ModelMapper(); // 自动映射同名属性 UserDTO dto = modelMapper.map(entity, UserDTO.class); UserVO vo = modelMapper.map(dto, UserVO.class); ``` 可以配置规则: ```java modelMapper.createTypeMap(UserEntity.class, UserVO.class) .addMappings(mapper -> { mapper.map(src -> src.getUsername(), UserVO::setUserName); mapper.when(Conditions.isNotNull()) .map(src -> src.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), UserVO::setCreateTimeFormatted); }); ``` ✅ 优点:简单快捷,支持动态配置 ❌ 缺点:运行时反射,性能略低;错误在运行时报出 --- ## 🔧 转换方法四:使用 Spring 的 `BeanUtils.copyProperties` 适用于字段名称一致的简单拷贝: ```java UserDTO dto = new UserDTO(); BeanUtils.copyProperties(entity, dto); // 同名属性自动复制 ``` ⚠️ 注意: - 不会自动跳过敏感字段(如 password),需手动排除 - 不支持嵌套对象深度拷贝 - 不支持类型不一致的字段(如 Date ↔ String) --- ## 🔧 转换方法五:Builder + 链式构造(函数式风格) ```java public static UserVO fromEntity(UserEntity e) { return UserVO.builder() .id(e.getId()) .userName(e.getUsername()) .createTimeFormatted(e.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))) .build(); } ``` 前提是你用了 Lombok: ```java @Data @Builder @NoArgsConstructor @AllArgsConstructor public class UserVO { private Long id; private String userName; private String createTimeFormatted; } ``` --- ## 🏁 最佳实践建议 | 场景 | 推荐方案 | |------|----------| | 大型项目、高性能要求 | ✅ **MapStruct** | | 小型项目、快速开发 | ✅ **ModelMapper** 或 **Lombok + Builder** | | 简单赋值 | ✅ `BeanUtils.copyProperties`(注意过滤) | | 安全性要求高(脱敏) | 手动映射 or MapStruct 显式控制 | | 微服务间传输 | DTO + 序列化(JSON/Protobuf) | --- ## ❗常见问题规避 1. **不要把 Entity 直接暴露给前端** → 会导致敏感字段泄露(如 password)、数据库结构暴露。 2. **避免双向转换陷阱** → 不要轻易将 VO → Entity 回写,容易引入非法数据。 3. **日期格式统一处理** → 使用 `@JsonFormat(pattern = "...")` 控制序列化输出。 4. **使用 IDE 插件辅助生成映射代码** → 如 IntelliJ 的 GenerateOLO plugin。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值