diff --git a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java index 29c5cb336b1..bd0fb0670b7 100644 --- a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java +++ b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/AbstractMongoRepositoryOperations.java @@ -163,24 +163,21 @@ protected R convertResult(CodecRegistry codecRegistry, if (resultType == BsonDocument.class) { return (R) result; } - Optional> introspection = BeanIntrospector.SHARED.findIntrospection(resultType); - if (introspection.isPresent()) { - try { - return mapIntrospectedObject(result, resultType); - } catch (Exception e) { - LOG.warn("Failed to map @Introspection annotated result. " + - "Now attempting to fallback and read object from the document. Error: {}", e.getMessage()); - } + + Optional maybeConverted = convertUsingIntrospected(result, resultType); + if (maybeConverted.isPresent()) { + return maybeConverted.get(); } + BsonValue value; if (result == null) { value = BsonNull.VALUE; } else if (result.size() == 1) { value = result.values().iterator().next(); } else if (result.size() == 2) { - Optional> id = result.entrySet().stream().filter(f -> !f.getKey().equals("_id")).findFirst(); - if (id.isPresent()) { - value = id.get().getValue(); + Optional> nonIdValue = result.entrySet().stream().filter(f -> !f.getKey().equals("_id")).findFirst(); + if (nonIdValue.isPresent()) { + value = nonIdValue.get().getValue(); } else { value = result.values().iterator().next(); } @@ -196,6 +193,42 @@ protected R convertResult(CodecRegistry codecRegistry, return conversionService.convertRequired(MongoUtils.toValue(value), resultType); } + /** + * Attempts to convert a BSON document into an instance of the specified result type using introspection. + * + * If the result type has been annotated with `@Introspection`, this method will attempt to use the provided + * `BeanIntrospection` to map the BSON document onto an instance of the result type. + * + * If the mapping fails or if no `BeanIntrospection` is found for the result type, an empty `Optional` is returned. + * + * @param result The BSON document containing the data to be mapped + * @param resultType The type of the object being mapped + * @param The type parameter representing the result type + * @return An `Optional` containing the mapped object, or an empty `Optional` if mapping failed or no `BeanIntrospection` was found + */ + protected Optional convertUsingIntrospected(BsonDocument result, Class resultType) { + Optional> introspection = BeanIntrospector.SHARED.findIntrospection(resultType); + if (introspection.isPresent()) { + try { + return Optional.of(mapIntrospectedObject(result, resultType)); + } catch (Exception e) { + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to map @Introspection annotated result. " + + "Now attempting to fallback and read object from the document. Error: {}", e.getMessage()); + } + } + } + return Optional.empty(); + } + + /** + * Maps an introspected object from a BSON document. + * + * @param result The BSON document containing the data to be mapped + * @param resultType The type of the object being mapped + * @param The type parameter representing the result type + * @return An instance of the specified result type, populated with data from the BSON document + */ private R mapIntrospectedObject(BsonDocument result, Class resultType) { return (new BeanIntrospectionMapper() { @Override diff --git a/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy b/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy index a0639c4c11d..888cde4f077 100644 --- a/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy +++ b/data-mongodb/src/test/groovy/io/micronaut/data/document/mongodb/MongoDocumentRepositorySpec.groovy @@ -10,8 +10,10 @@ import com.mongodb.client.model.Updates import groovy.transform.Memoized import io.micronaut.data.document.mongodb.entities.ComplexEntity import io.micronaut.data.document.mongodb.entities.ComplexValue +import io.micronaut.data.document.mongodb.entities.Customer import io.micronaut.data.document.mongodb.entities.ElementRow import io.micronaut.data.document.mongodb.repositories.ComplexEntityRepository +import io.micronaut.data.document.mongodb.repositories.CustomerRepository import io.micronaut.data.document.mongodb.repositories.ElementRowRepository import io.micronaut.data.document.mongodb.repositories.MongoAuthorRepository import io.micronaut.data.document.mongodb.repositories.MongoCriteriaPersonRepository @@ -892,6 +894,23 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme people.collect{ it.id }.containsAll(limitedPeople2.collect{ it.id }) } + void "test DTO retrieval"() { + when: + def customer = new Customer("1", "first", "last", List.of()) + def saved = customerRepository.save(customer) + def loaded = customerRepository.findById(saved.id) + then: + loaded.present + loaded.get().id == saved.id + when: + def customerViews = customerRepository.viewFindAll(); + then: + customerViews.size() == 1 + customerViews[0].id == saved.id + cleanup: + customerRepository.deleteAll() + } + @Memoized MongoExecutorPersonRepository getMongoExecutorPersonRepository() { return context.getBean(MongoExecutorPersonRepository) @@ -959,4 +978,9 @@ class MongoDocumentRepositorySpec extends AbstractDocumentRepositorySpec impleme ComplexEntityRepository getComplexEntityRepository() { return context.getBean(ComplexEntityRepository) } + + @Memoized + CustomerRepository getCustomerRepository() { + return context.getBean(CustomerRepository) + } } diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ChangeLog.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ChangeLog.java new file mode 100644 index 00000000000..996bc27e4a8 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/ChangeLog.java @@ -0,0 +1,4 @@ +package io.micronaut.data.document.mongodb.entities; + +public record ChangeLog(String message) { +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/Customer.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/Customer.java new file mode 100644 index 00000000000..d919f8c5d96 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/Customer.java @@ -0,0 +1,60 @@ +package io.micronaut.data.document.mongodb.entities; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; + +import java.util.List; + +@MappedEntity +public final class Customer { + @Id + @GeneratedValue + private String id; + private String firstName; + private String lastName; + + private List changeLogs; + + public Customer() { + } + + public Customer(String id, String firstName, String lastName, List changeLogs) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.changeLogs = changeLogs; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public List getChangeLogs() { + return changeLogs; + } + + public void setChangeLogs(List changeLogs) { + this.changeLogs = changeLogs; + } +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/CustomerView.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/CustomerView.java new file mode 100644 index 00000000000..916c95e43fd --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/entities/CustomerView.java @@ -0,0 +1,11 @@ +package io.micronaut.data.document.mongodb.entities; + +import io.micronaut.core.annotation.Introspected; +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonRepresentation; + +import static org.bson.BsonType.OBJECT_ID; + +@Introspected +public record CustomerView(@BsonId @BsonRepresentation(OBJECT_ID) String id, String firstName, String lastName) { +} diff --git a/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/CustomerRepository.java b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/CustomerRepository.java new file mode 100644 index 00000000000..ca014e887c6 --- /dev/null +++ b/data-mongodb/src/test/java/io/micronaut/data/document/mongodb/repositories/CustomerRepository.java @@ -0,0 +1,16 @@ +package io.micronaut.data.document.mongodb.repositories; + +import io.micronaut.data.document.mongodb.entities.Customer; +import io.micronaut.data.document.mongodb.entities.CustomerView; +import io.micronaut.data.mongodb.annotation.MongoFindQuery; +import io.micronaut.data.mongodb.annotation.MongoRepository; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; + +@MongoRepository +public interface CustomerRepository extends CrudRepository { + + @MongoFindQuery(filter = "{}", project = "{ changeLogs: 0}") + List viewFindAll(); +}