Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DTO joins support issues #2761

Draft
wants to merge 10 commits into
base: 4.10.x
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -792,11 +792,12 @@ private boolean isDtoType(ClassElement repositoryElement, ClassElement classElem
/**
* Find DTO properties.
*
* @param matchContext The method match context
* @param entity The entity
* @param returnType The result
* @return DTO properties
*/
protected List<SourcePersistentProperty> getDtoProjectionProperties(SourcePersistentEntity entity,
protected List<SourcePersistentProperty> getDtoProjectionProperties(MethodMatchContext matchContext, SourcePersistentEntity entity,
ClassElement returnType) {
return returnType.getBeanProperties().stream()
.filter(dtoProperty -> {
Expand Down Expand Up @@ -825,8 +826,13 @@ protected List<SourcePersistentProperty> getDtoProjectionProperties(SourcePersis
// Convert anything to a string or an object
return pp;
}
if (!TypeUtils.areTypesCompatible(dtoPropertyType, pp.getType())) {
throw new MatchFailedException("Property [" + propertyName + "] of type [" + dtoPropertyType.getName() + "] is not compatible with equivalent property of type [" + pp.getType().getName() + "] declared in entity: " + entity.getName());
boolean compatibleTypes = TypeUtils.areTypesCompatible(dtoPropertyType, pp.getType());
if (!compatibleTypes) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be the fix for initial issue. If field types are not simple field or same classes, then check if entity matches with DTO.
After this, new error is when reading data

io.micronaut.data.exceptions.DataAccessException: Error reading object for name [author] from result set: Column "author" not found [42122-224]

because mapper tries to read it into the field vs into joined object. Also, should method specify join type for the DTO or automatically assume join is requested?

// Check if these are compatible non-simple field types (kind of nested DTOs)
List<SourcePersistentProperty> props = getDtoProjectionProperties(matchContext, new SourcePersistentEntity(pp.getType(), matchContext::getEntity), dtoPropertyType);
if (props.isEmpty()) {
throw new MatchFailedException("Property [" + propertyName + "] of type [" + dtoPropertyType.getName() + "] is not compatible with equivalent property of type [" + pp.getType().getName() + "] declared in entity: " + entity.getName());
}
}
return pp;
}).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ protected MethodMatchInfo build(MethodMatchContext matchContext) {
);

if (result.isDto() && !result.isRuntimeDtoConversion()) {
List<SourcePersistentProperty> dtoProjectionProperties = getDtoProjectionProperties(matchContext.getRootEntity(), resultType);
List<SourcePersistentProperty> dtoProjectionProperties = getDtoProjectionProperties(matchContext, matchContext.getRootEntity(), resultType);
if (!dtoProjectionProperties.isEmpty()) {
List<Selection<?>> selectionList = dtoProjectionProperties.stream()
.map(p -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ protected MethodMatchInfo build(MethodMatchContext matchContext) {
);

if (result.isDto() && !result.isRuntimeDtoConversion()) {
List<SourcePersistentProperty> dtoProjectionProperties = getDtoProjectionProperties(matchContext.getRootEntity(), resultType);
List<SourcePersistentProperty> dtoProjectionProperties = getDtoProjectionProperties(matchContext, matchContext.getRootEntity(), resultType);
if (!dtoProjectionProperties.isEmpty()) {
Root<?> root = query.getRoots().iterator().next();
List<Selection<?>> selectionList = dtoProjectionProperties.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ protected MethodMatchInfo build(MethodMatchContext matchContext) {
);

if (result.isDto() && !result.isRuntimeDtoConversion()) {
List<SourcePersistentProperty> dtoProjectionProperties = getDtoProjectionProperties(matchContext.getRootEntity(), resultType);
List<SourcePersistentProperty> dtoProjectionProperties = getDtoProjectionProperties(matchContext, matchContext.getRootEntity(), resultType);
if (!dtoProjectionProperties.isEmpty()) {
List<Selection<?>> selectionList = dtoProjectionProperties.stream()
.map(p -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ import io.micronaut.data.tck.entities.Person
import io.micronaut.data.tck.entities.Student
import io.micronaut.data.tck.entities.TimezoneBasicTypes
import io.micronaut.data.tck.jdbc.entities.Role
import io.micronaut.data.tck.jdbc.entities.User
import io.micronaut.data.tck.jdbc.entities.UserRole
import io.micronaut.data.tck.jdbc.entities.UserRoleId
import io.micronaut.data.tck.repositories.*
import io.micronaut.transaction.SynchronousTransactionManager
import io.micronaut.transaction.TransactionCallback
Expand Down Expand Up @@ -1332,6 +1330,18 @@ abstract class AbstractRepositorySpec extends Specification {
authors.forEach { assert it.books.size() == 0 }
}

void "test DTO with nested DTO"() {
given:
saveSampleBooks()

when:
def optBook = bookRepository.queryByTitleContains("Stand")

then:
optBook.present
optBook.get().author
}

void "stream joined"() {
if (!transactionManager.isPresent()) {
return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.data.tck.entities;

import io.micronaut.core.annotation.Introspected;

import java.time.LocalDateTime;

@Introspected
public class BookDtoWithAuthorDto {

private String title;
private int totalPages;
private LocalDateTime lastUpdated;

private AuthorDTO author;

public BookDtoWithAuthorDto() {
}

public BookDtoWithAuthorDto(String title, int totalPages) {
this.title = title;
this.totalPages = totalPages;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public int getTotalPages() {
return totalPages;
}

public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}

public LocalDateTime getLastUpdated() {
return lastUpdated;
}

public void setLastUpdated(LocalDateTime lastUpdated) {
this.lastUpdated = lastUpdated;
}

public AuthorDTO getAuthor() {
return author;
}

public void setAuthor(AuthorDTO author) {
this.author = author;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.micronaut.data.tck.entities.AuthorBooksDto;
import io.micronaut.data.tck.entities.Book;
import io.micronaut.data.tck.entities.BookDto;
import io.micronaut.data.tck.entities.BookDtoWithAuthorDto;
import io.micronaut.data.tck.entities.Genre;

import java.util.ArrayList;
Expand Down Expand Up @@ -172,4 +173,6 @@ protected Book newBook(Author author, String title, int pages) {
abstract List<Book> findByTitleInAndTotalPagesGreaterThan(List<String> titles, int totalPages);

abstract Long countByTitleInAndTotalPagesGreaterThan(List<String> titles, int totalPages);

abstract Optional<BookDtoWithAuthorDto> queryByTitleContains(String title);
Copy link
Contributor Author

@radovanradic radovanradic Jan 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having AuthorDto as BookDto field should work but compiler gives this error

error: Unable to implement Repository method: PostgresBookRepository.queryByTitleContains(String title). Property [author] of type [io.micronaut.data.tck.entities.AuthorDTO] is not compatible with equivalent property of type [io.micronaut.data.tck.entities.Author] declared in entity: io.micronaut.data.tck.entities.Book

So we might need to check nested types if compatible. I will commit potential fix for this, but still SELECT does not use this joined entity and mapper also does not consider it when reading data.

}
Loading