Jackson配置问题:密码仅在DTO转Entity传递,不返回前端
问题原因分析
你遇到的问题核心是@JsonProperty(access = Access.WRITE_ONLY)的作用逻辑:这个注解限制字段只能被反序列化(JSON转对象),不能被序列化(对象转JSON)。而你用mapper.convertValue(userDto, User.class)时,本质是先把UserDto序列化成Jackson的中间数据结构,再反序列化成User对象——这一步需要读取UserDto的password字段(属于序列化操作),但WRITE_ONLY规则阻止了这个读取行为,导致密码无法传递到User实体中。
下面提供三种可行的解决方案,按推荐优先级排序:
方案1:拆分请求/响应DTO(推荐)
遵循单一职责原则,创建两个独立的DTO分别处理请求接收和响应返回,彻底避免字段冲突:
定义新的DTO
// 仅用于接收创建用户的请求 @Data @NoArgsConstructor @AllArgsConstructor public class CreateUserRequest { private String username; private String password; // 无需加WRITE_ONLY,因为仅作为请求体使用 } // 仅用于返回用户响应 @Data @NoArgsConstructor @AllArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class UserResponse { private String username; private Date createDate; }
修改Controller代码
public class UserController { @Autowired private ObjectMapper objectMapper; @PostMapping("/user") public UserResponse addUser(CreateUserRequest request) { // 请求DTO转User实体 User user = objectMapper.convertValue(request, User.class); User savedUser = userService.add(user); // 保存后的实体转响应DTO return objectMapper.convertValue(savedUser, UserResponse.class); } }
这种方式逻辑清晰,后续扩展字段时不会相互影响,是最规范的做法。
方案2:使用Jackson的@JsonView
通过视图控制不同场景下的字段可见性,无需拆分DTO:
定义视图接口
public class Views { // 用于写入场景:接收请求、DTO转实体 public interface Write {} // 用于读取场景:返回响应 public interface Read {} }
修改UserDto
@AllArgsConstructor @Data @JsonIgnoreProperties(ignoreUnknown = true) @JsonInclude(JsonInclude.Include.NON_NULL) @NoArgsConstructor public class UserDto { private String username; // 仅在Write视图下可见(可被读取用于转实体),Read视图下隐藏 @JsonView(Views.Write.class) private String password; // 仅在Read视图下可见(响应返回) @JsonView(Views.Read.class) private Date createDate; }
修改Controller
public class UserController { @Autowired private ObjectMapper objectMapper; @PostMapping("/user") @JsonView(Views.Read.class) // 响应时仅返回Read视图包含的字段 public UserDto addUser(@JsonView(Views.Write.class) UserDto userDto) { // 此时userDto包含password,可正常转实体 User user = objectMapper.convertValue(userDto, User.class); User savedUser = userService.add(user); // 转回DTO后,password会被Read视图忽略 return objectMapper.convertValue(savedUser, UserDto.class); } }
注意:必须使用Spring注入的ObjectMapper,而非手动new的实例,才能正常识别@JsonView注解。
方案3:手动字段映射/使用MapStruct
如果不想调整DTO结构,可通过手动赋值或映射工具控制字段传递:
手动映射示例
public class UserController { @PostMapping("/user") public UserDto addUser(UserDto userDto) { // 手动将DTO字段赋值给实体,不受Jackson注解限制 User user = new User(); user.setUsername(userDto.getUsername()); user.setPassword(userDto.getPassword()); User savedUser = userService.add(user); // 手动构造响应DTO,不设置password UserDto responseDto = new UserDto(); responseDto.setUsername(savedUser.getUsername()); responseDto.setCreateDate(savedUser.getCreateDate()); return responseDto; } }
MapStruct映射示例
先添加MapStruct依赖,然后定义映射接口:
@Mapper(componentModel = "spring") public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); // DTO转实体,自动映射所有字段(包括password) User toEntity(UserDto userDto); // 实体转DTO,忽略password字段 @Mapping(target = "password", ignore = true) UserDto toDto(User user); }
Controller中使用:
public class UserController { @Autowired private UserMapper userMapper; @PostMapping("/user") public UserDto addUser(UserDto userDto) { User user = userMapper.toEntity(userDto); User savedUser = userService.add(user); return userMapper.toDto(savedUser); } }
内容的提问来源于stack exchange,提问作者Dannenberger
相关产品推荐
相关产品推荐

