问题
I'm working on an API endpoint that returns a Springframework Page response. I want the front end to be able to sort the data but I can't expect the front end to know that the column they want to sort on is actually inside a composite primary key.
In the example below (a simplified version of what I'm working on) you can see that the startDate
column is inside a RouteEntityPk
class, which is linked to the RouteEntity
class with the @EmbeddedId
annotation. To Sort on that column the front end would need to add ?sort=pk.startdate,asc
to the request. I want the front end to only have to provide ?sort=startdate,asc
.
Is there a way - using Spring magic - of having the repository know that startdate
== pk.startdate
, or will I have to write a translator which will remove the pk
when showing the sort column to the front end, and add it where necessary when reading it from the request?
Controller:
@GetMapping(value = "routes/{routeId}", produces = APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Page<Route>> getRouteByRouteId(@PathVariable(value = "routeId") final String routeId,
@PageableDefault(size = 20) @SortDefault.SortDefaults({
@SortDefault(sort = "order", direction = Sort.Direction.DESC),
@SortDefault(sort = "endDate", direction = Sort.Direction.DESC)
}) final Pageable pageable) {
return ResponseEntity.ok(routeService.getRouteByRouteId(routeId, pageable));
}
Service:
public Page<Route> getRouteByRouteId(String routeId, Pageable pageable) {
Page<RouteEntity> routeEntities = routeRepository.findByRouteId(routeId, pageable);
return new PageImpl<>(
Collections.singletonList(routeTransformer.toRoute(routeId, routeEntities)),
pageable,
routeEntities.getContent().size()
);
}
Repository:
@Repository
public interface RouteRepository extends JpaRepository<RouteEntity, RouteEntityPk> {
@Query(value = " SELECT re FROM RouteEntity re"
+ " AND re.pk.routeId = :routeId")
Page<RouteEntity> findByRouteId(@Param("routeId") final String routeId,
Pageable pageable);
}
Entities:
Route:
@Data
@Entity
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "ROUTE", schema = "NAV")
public class RouteEntity {
@EmbeddedId
private RouteEntityPk pk;
@Column(name = "NAME")
private String name;
@Column(name = "ORDER")
private Integer order;
@Column(name = "END_DTE")
private LocalDate endDate;
}
RoutePk:
@Data
@Builder(toBuilder = true)
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
public class RouteEntityPk implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "ROUTE_ID")
private String routeId;
@Column(name = "STRT_DTE")
private LocalDate startDate;
}
Models:
Route:
@Data
@Builder
public class Route {
public String name;
public String routeId;
public List<RouteItem> items;
}
Item:
@Data
@Builder
public class Item {
public Integer order;
public LocalDate startDate;
public LocalDate endDate;
}
Transformer:
public Route toRoute(String routeId, Page<RouteEntity> routeEntities) {
return Route.builder()
.name(getRouteName(routeEntities))
.routeId(routeId)
.items(routeEntities.getContent().stream()
.map(this::toRouteItem)
.collect(Collectors.toList()))
.build();
}
private Item toRouteItem(RouteEntity item) {
return ParcelshopDrop.builder()
.order(item.getOrder())
.startDate(item.getStartDate())
.endDate(item.getEndDate())
.build();
}
回答1:
So it looks like the way to do this is to use the other way you can deal with composite primary key's in JPA, the annotation @IdClass
. This way you can put the fields in the main entity and refer to them as such.
Below is a link to the baeldung article I followed and the changes to the entities I posted above that make this work:
https://www.baeldung.com/jpa-composite-primary-keys
Entities:
Route:
@Data
@Entity
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@IdClass(RouteEntityPk.class)
@Table(name = "ROUTE", schema = "NAV")
public class RouteEntity {
@Id
@Column(name = "ROUTE_ID")
private String routeId;
@Id
@Column(name = "STRT_DTE")
private LocalDate startDate;
@Column(name = "NAME")
private String name;
@Column(name = "ORDER")
private Integer order;
@Column(name = "END_DTE")
private LocalDate endDate;
}
RoutePk:
@Data
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class RouteEntityPk implements Serializable {
private static final long serialVersionUID = 1L;
private String routeId;
private LocalDate startDate;
}
回答2:
This is one solution, probably not the best, but you can transform Pageable
object in order to replace the field name like this :
In your controller getRouteByRouteId
method :
List<Order> orders = pageable.getSort().stream().map(o -> o.getProperty().equals("startdate") ? new Order(o.getDirection(), "pk.startdate"): o).collect(Collectors.toList());
Then you can call the service with the modified object :
return ResponseEntity.ok(routeService.getRouteByRouteId(routeId, PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders))));
来源:https://stackoverflow.com/questions/57771587/not-exposing-the-path-of-entities-that-have-a-composite-primary-key-to-the-front