I have a entity Author that has a collection of Books with property publishedDate.
Each Author has a collection books and they are mapped like so:
public class Author {
@Id
private Long id;
private String name;
@JsonBackReference
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "author")
private List<Book> listBook = new ArrayList<>();
// other fields
// getters and setters
Book
public class Book {
@Id
private Long id;
@ManyToOne(fetch = FetchType.EAGER, optional = false)
private Author author;
private LocalDateTime publishedDateTime;
I would like to get a Author By Id with the newest Book as a Dto, i.e.
@Data
public AuthorNewestBookDto {
Long authorId;
String name;
Book newestBook;
Cast the results of the query directly into the Dto. No mapper required. Efficient transaction on database. Similar to here.
Use a mapstruct mapper to return an Author entity with a collection of books and use the @Before annotation to return only the single newest book from the collection. Similar to here.
Is there a better way to do this conversion to AuthorNewestBookDto where I need to retrieve the single newest member of a collection into a nested singular Dto.
I have considered if I could do it in hibernate stage with a DtoResultTransformer (Hibernate 6).
Hopefully this is not too opinionated for Stack Overflow.
MapStruct has multiple tools for situations like this:
Qualifier
We can provide custom mapping logic between Entity field type: List<Book>
and DTO field type Book
:
interface BookingMapping {
@Mapping(target = "newestBook", source = "listBook", qualifiedBy=NewestBookQualifier.class)
//.. other fields
AuthorNewestBookDto toDtoQualifiedBy(Author entity);
}
...
public class BookingMappingUtil {
@NewestBookQualifier
BookDto toNewestBook(List<Book> listBook) {
return listBook.stream()
.max(Comparator.comparing(Book::getPublishedDateTime))
.orElse(null)
}
...
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface NewestBookQualifier{
}
Iterable to non-iterable example (official MapStruct-Example project).
@Named
In this case, we don't need to create a new annotation. Named-annotation documentation
@Mapping(target = "newestBook", source = "listBook", qualifiedByName="toNewestBook" )
//.. other fields
AuthorNewestBookDto toDtoQualifiedByName(Author entity);
@Named("toNewestBook")
default BookDto toNewestBook(List<Book> listBook) {
return listBook.stream()
.max(Comparator.comparing(Book::getPublishedDateTime))
.orElse(null)
}
@Mapping(.. expression=".." ..)
We are able to store business logic as a String. This approach can be not readable but very easy to implement. expressions documentation
@Mapping(target = "newestBook",
source = "listBook",
expression = "java( listBook.stream().max(java.util.Comparator.comparing(Book::getPublishedDateTime)).orElse(null) )" )
//.. other fields
AuthorNewestBookDto toDtoQualifiedByName(Author entity);