Spring Boot如何实现类似projections的低成本灵活DTO构建?
结论
可以直接定义仅包含所需字段的接口作为方法参数或返回值,这是Spring Boot生态下实现类投影效果、减少DTO模板代码的最优方案之一,除此之外还有多个低成本实现方案可供选择:
方案1:接口投影(零实现成本,适配Spring Data Projections使用习惯)
Spring MVC内置的Jackson序列化/反序列化模块原生支持接口类型处理,你不需要编写任何接口实现类、getter/setter模板代码,仅需要定义携带对应get方法的接口即可:
// 仅返回id和登录名的投影接口 public interface UserLoginProjection { UUID getId(); String getLogin(); } // 包含id、登录名、邮箱的投影接口 public interface UserLoginEmailProjection { UUID getId(); String getLogin(); String getEmail(); }
直接在Controller中替换原有DTO类即可正常使用:
@RestController("/users") public class UserController { @PutMapping public UserLoginProjection someRequest(@RequestBody UserLoginEmailProjection user) { // 直接调用接口的get方法即可获取请求参数,返回值也可直接返回投影实例 } }
如果和Spring Data JPA结合使用,还可以直接将投影接口作为Repository查询方法的返回值,省去实体到DTO的手动字段拷贝步骤:
public interface UserRepository extends JpaRepository<User, UUID> { // 动态返回指定类型的投影 <T> T findById(UUID id, Class<T> projectionType); }
需要新增字段时,仅需要在对应接口中新增对应get方法即可,维护成本极低。
方案2:Java Record类(Java 16+推荐,支持扩展能力)
如果你需要给DTO添加字段校验、自定义逻辑等能力,接口投影的灵活性不足,可以使用Java 16引入的Record类替代传统DTO类,无需手动编写getter、setter、构造方法、equals/hashCode等模板代码:
// 仅返回id和登录名的Record public record UserLoginRecord(UUID id, String login) {} // 包含id、登录名、邮箱的Record public record UserLoginEmailRecord(UUID id, String login, String email) {}
用法和普通DTO类完全一致,新增字段仅需要在构造参数中添加对应项即可,同时支持直接添加@NotBlank等校验注解,适配更多业务场景。
方案3:动态映射工具(适合多投影场景复用转换逻辑)
如果业务中存在大量不同字段组合的投影场景,不想重复定义大量接口/Record,可以结合MapStruct、ModelMapper等映射工具,统一配置实体到不同投影的转换规则,仅需要定义投影类/接口,转换逻辑由工具自动生成,进一步降低重复编码成本。
内容的提问来源于stack exchange,提问作者Igor Chestnov
相关产品推荐
相关产品推荐

