在Spring开发中将DTO 映射到 Entity 的三种常用方式

在 Spring 开发中,将 DTO(数据传输对象)映射到 Entity(实体类)是常见需求,特别是在分层架构中(如 Controller、Service、Repository)。以下是基于 UserEntity, RoleEntity, UserDTO, UserController, UserService, 和 UserRepository 的示例,展示几种常用的 DTO 到 Entity 映射方式,包括手动映射、Spring 的 BeanUtils、以及 MapStruct 等工具的使用场景和实现。


示例背景

假设我们有以下类:

  • UserEntity:用户实体类
  • RoleEntity:角色实体类
  • UserDTO:用户数据传输对象
  • UserController:控制器
  • UserService:服务层
  • UserRepository:数据访问层

1. 数据模型定义

数据模型是关系数据库表到Java代码的映射。

UserEntity

import lombok.Data;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "users")
@Data
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    private String email;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<RoleEntity> roles = new HashSet<>();

    private LocalDateTime createdAt;
}

RoleEntity

import lombok.Data;
import javax.persistence.*;

@Entity
@Table(name = "roles")
@Data
public class RoleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    private String description;
}

2. 传输数据模型定义

传输数据模型是来自前端界面用户输入的数据到Java代码的映射。

UserDTO

import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.util.Set;

@Data
public class UserDTO {
    private Long id;

    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;

    private String email;

    private Set<String> roles; // 角色名称集合
}


3. 常用映射方式

方法 1:手动映射

手动映射是最简单直接的方式,适合字段较少或需要自定义逻辑的场景。

UserService 示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public UserEntity createUser(UserDTO userDTO) {
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername(userDTO.getUsername());
        userEntity.setPassword(passwordEncoder.encode(userDTO.getPassword())); // 加密密码
        userEntity.setEmail(userDTO.getEmail());
        userEntity.setCreatedAt(LocalDateTime.now());

        // 处理角色(假设角色已存在数据库)
        if (userDTO.getRoles() != null && !userDTO.getRoles().isEmpty()) {
            Set<RoleEntity> roles = new HashSet<>();
            for (String roleName : userDTO.getRoles()) {
                RoleEntity role = new RoleEntity();
                role.setName(roleName);
                roles.add(role); // 这里简化,实际应从数据库查询
            }
            userEntity.setRoles(roles);
        }

        return userRepository.save(userEntity);
    }
}

优点:

  • 完全控制映射逻辑。
  • 适合复杂业务逻辑。

缺点:

  • 代码量多,容易出错,维护成本高。

方法 2:使用 Spring 的 BeanUtils

Spring 的 BeanUtils 可以快速拷贝相同字段名的属性,减少手动赋值的工作量。

UserService 示例

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public UserEntity createUser(UserDTO userDTO) {
        UserEntity userEntity = new UserEntity();
        BeanUtils.copyProperties(userDTO, userEntity); // 拷贝相同字段名的属性

        // 处理密码加密和额外字段
        userEntity.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        userEntity.setCreatedAt(LocalDateTime.now());

        // 处理角色
        if (userDTO.getRoles() != null && !userDTO.getRoles().isEmpty()) {
            Set<RoleEntity> roles = new HashSet<>();
            for (String roleName : userDTO.getRoles()) {
                RoleEntity role = new RoleEntity();
                role.setName(roleName);
                roles.add(role); // 实际应查询数据库
            }
            userEntity.setRoles(roles);
        }

        return userRepository.save(userEntity);
    }
}

优点:

  • 减少手动赋值的代码量。
  • 简单易用。

缺点:

  • 仅支持字段名完全匹配的拷贝。
  • 无法处理复杂映射(如 Set<String>Set<RoleEntity>)。

方法 3:使用 MapStruct

MapStruct 是目前最推荐的方式,它通过注解生成高效的映射代码,支持复杂映射和自定义逻辑。

3.1 添加 MapStruct 依赖

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
    <scope>provided</scope>
</dependency>

3.2 定义映射接口 (UserMapper.java)

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;

import java.time.LocalDateTime;
import java.util.Set;
import java.util.stream.Collectors;

@Mapper(componentModel = "spring")
public interface UserMapper {

    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(target = "password", source = "password") // 密码需额外处理
    @Mapping(target = "createdAt", expression = "java(java.time.LocalDateTime.now())")
    @Mapping(target = "roles", source = "roles", qualifiedByName = "mapRoles")
    UserEntity toEntity(UserDTO userDTO);

    @Mapping(target = "roles", source = "roles", qualifiedByName = "mapRoleNames")
    UserDTO toDto(UserEntity userEntity);

    @Named("mapRoles")
    default Set<RoleEntity> mapRoles(Set<String> roleNames) {
        if (roleNames == null) return null;
        return roleNames.stream()
            .map(name -> {
                RoleEntity role = new RoleEntity();
                role.setName(name);
                return role; // 实际应从数据库查询
            })
            .collect(Collectors.toSet());
    }

    @Named("mapRoleNames")
    default Set<String> mapRoleNames(Set<RoleEntity> roles) {
        if (roles == null) return null;
        return roles.stream()
            .map(RoleEntity::getName)
            .collect(Collectors.toSet());
    }
}

3.3 UserService 示例

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public UserDTO createUser(UserDTO userDTO) {
        UserEntity userEntity = userMapper.toEntity(userDTO);
        userEntity.setPassword(passwordEncoder.encode(userDTO.getPassword())); // 加密密码
        UserEntity savedEntity = userRepository.save(userEntity);
        return userMapper.toDto(savedEntity);
    }
}

优点:

  • 自动生成映射代码,减少手动工作。
  • 支持复杂映射和自定义逻辑。
  • 与 Spring 集成良好,可注入依赖。

缺点:

  • 需额外引入依赖和学习成本。

4. 完整示例

UserRepository

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<UserEntity, Long> {
}

UserController

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping
    public ResponseEntity<UserDTO> createUser(@Valid @RequestBody UserDTO userDTO) {
        UserDTO createdUser = userService.createUser(userDTO);
        return ResponseEntity.ok(createdUser);
    }
}

注意事项

  • 角色处理:上述示例中角色直接创建为新对象,实际开发中应从数据库查询已有角色(如通过 RoleRepository)。
  • 密码加密:始终在服务层对密码进行加密处理。
  • 双向映射:MapStruct 支持 Entity 到 DTO 的反向映射,方便返回数据。

5. 选择建议

方式适用场景优点缺点
手动映射小型项目或复杂逻辑灵活,易调试代码冗余,维护成本高
BeanUtils字段名一致的简单映射简单,代码少不支持复杂映射
MapStruct中大型项目,复杂映射高效,自动生成,支持扩展需学习和配置依赖

推荐:对于中小型项目,BeanUtils 结合少量手动逻辑即可;对于大型项目或需要长期维护的系统,强烈推荐使用 MapStruct,因其扩展性和可维护性更强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值