JPA @ManyToOne关联场景下如何让Swagger请求体仅传关联对象ID?
解决方案
这里提供两种可直接落地的方案,按需选择即可:
方案1:使用DTO层做入参转换(推荐)
这是业界通用的最佳实践,不要直接把JPA实体作为接口入参,单独定义接口层数据传输对象(DTO),完全解耦API定义和数据库实体结构,后续接口和实体变更互不影响。
- 新增DTO类
import javax.validation.constraints.Future; import javax.validation.constraints.NotNull; import java.util.Date; public class BookingCreateDTO { @NotNull private Long customerId; @NotNull private Long taxiId; @NotNull @Future(message = "Bookings can not be in the past. Please choose one from the future") private Date bookingDate; // 省略getter、setter方法 }
- 修改RestService层入参和转换逻辑
public Response createBooking( @ApiParam(value = "Customer ID, Taxi ID and a booking date required to create a booking", required = true) BookingCreateDTO dto) { if (dto == null) { throw new RestServiceException("Bad Request", Response.Status.BAD_REQUEST); } // 转换DTO为JPA实体 Booking booking = new Booking(); // getReference只会生成代理对象不会查询数据库,性能更高 Customer customer = em.getReference(Customer.class, dto.getCustomerId()); Taxi taxi = em.getReference(Taxi.class, dto.getTaxiId()); booking.setCustomer(customer); booking.setTaxi(taxi); booking.setBookingDate(dto.getBookingDate()); Response.ResponseBuilder builder; // 后续原有业务逻辑完全不变,直接用booking对象调用service.create即可 try { service.create(booking); builder = Response.status(Response.Status.CREATED).entity(booking); // 省略原有异常处理逻辑 // ... }
改完后Swagger会自动根据DTO结构生成你期望的请求体示例。
方案2:直接修改Booking实体适配序列化
如果不想新增DTO类,可以通过Jackson和Swagger注解直接修改实体,无需改动原有业务逻辑。
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModelProperty; public class Booking implements Serializable { @Id @ApiModelProperty(readOnly = true) @GeneratedValue(strategy = GenerationType.TABLE) private Long id; // 给原有关联字段加隐藏注解 @ApiModelProperty(hidden = true) @JsonIgnore // 序列化/反序列化都忽略该字段 @NotNull @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.DETACH) @JoinColumn(name = "customer_id") private Customer customer; @ApiModelProperty(hidden = true) @JsonIgnore @NotNull @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.DETACH) @JoinColumn(name = "taxi_id") private Taxi taxi; // 新增反序列化用的ID字段 @NotNull @JsonProperty("customer_id") private Long customerId; @NotNull @JsonProperty("taxi_id") private Long taxiId; @NotNull @Future(message = "Bookings can not be in the past. Please choose one from the future") @Column(name = "booking_date") @Temporal(TemporalType.DATE) private Date bookingDate; // 省略原有getter、setter,新增字段的getter、setter }
然后在业务Service层create方法最前面补充关联对象填充逻辑:
Booking create(Booking booking) throws ConstraintViolationException, ValidationException, Exception { // 新增填充逻辑 Customer customer = em.getReference(Customer.class, booking.getCustomerId()); Taxi taxi = em.getReference(Taxi.class, booking.getTaxiId()); booking.setCustomer(customer); booking.setTaxi(taxi); // 原有逻辑完全不变 log.info("BookingService Testing: " + booking.toString()); log.info("BookingService.create() - Creating " + booking.getCustomer().getId() + " " + booking.getTaxi().getId()); return crud.create(booking); }
补充注意事项
如果需要校验传入的customerId、taxiId对应记录真实存在,把em.getReference替换为对应Repository的findById方法,查询结果为空时直接抛出参数异常即可,避免持久化时报外键约束错误。
内容的提问来源于stack exchange,提问作者Sondergaard
相关产品推荐
相关产品推荐

