diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/Course.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/Course.java new file mode 100644 index 0000000000..7170b1a3ef --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/Course.java @@ -0,0 +1,24 @@ +package io.micronaut.data.jdbc.h2.remap; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.Relation; + +import java.util.List; +import java.util.UUID; + +import static io.micronaut.data.annotation.Relation.Cascade.ALL; +import static io.micronaut.data.annotation.Relation.Kind.MANY_TO_MANY; + +@MappedEntity("att_course") +record Course( + + @Id + UUID id, + + String name, + + @Relation(value = MANY_TO_MANY, mappedBy = "courses", cascade = ALL) + List students +) { +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/CourseRepository.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/CourseRepository.java new file mode 100644 index 0000000000..7715dc75da --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/CourseRepository.java @@ -0,0 +1,16 @@ +package io.micronaut.data.jdbc.h2.remap; + +import io.micronaut.data.annotation.Join; +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +import java.util.List; +import java.util.UUID; + +@JdbcRepository(dialect = Dialect.H2) +interface CourseRepository extends CrudRepository { + + @Join("students") + List findStudentsById(UUID id); +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/ManyToManyAttributeSpec.groovy b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/ManyToManyAttributeSpec.groovy new file mode 100644 index 0000000000..0e5d2f1432 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/ManyToManyAttributeSpec.groovy @@ -0,0 +1,49 @@ +package io.micronaut.data.jdbc.h2.remap + +import io.micronaut.data.jdbc.h2.H2DBProperties +import io.micronaut.test.extensions.spock.annotation.MicronautTest +import jakarta.inject.Inject +import spock.lang.Specification + +@H2DBProperties +@MicronautTest +class ManyToManyAttributeSpec extends Specification { + + @Inject + CourseRepository courseRepository + @Inject + StudentRepository studentRepository + + def "works - should create a student"() { + when: + Student student = new Student( + new StudentId(UUID.randomUUID()), + "test", + List.of() + ) + studentRepository.save(student) + then: + studentRepository.findById(student.id()).get() == student + } + + def "should find students attending a course"() { + when: + Course course = new Course( + UUID.randomUUID(), + "computer science", + List.of() + ) + courseRepository.save(course) + // create a new student and join the existing course + Student student = new Student( + new StudentId(UUID.randomUUID()), + "test", + List.of(course) + ) + studentRepository.save(student) == student + + then: + // we should now be able to find the student that attends the course + courseRepository.findStudentsById(course.id())[0].name() == student.name() + } +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/Student.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/Student.java new file mode 100644 index 0000000000..dd3c83644f --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/Student.java @@ -0,0 +1,26 @@ +package io.micronaut.data.jdbc.h2.remap; + +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.data.annotation.Relation; +import io.micronaut.data.model.DataType; + +import java.util.List; + +import static io.micronaut.data.annotation.Relation.Cascade.ALL; +import static io.micronaut.data.annotation.Relation.Kind.MANY_TO_MANY; + +@MappedEntity("att_student") +record Student( + + @Id + @MappedProperty(converter = StudentIdAttributeConverter.class, type = DataType.UUID) + StudentId id, + + String name, + + @Relation(value = MANY_TO_MANY, cascade = ALL) + List courses +) { +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/StudentId.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/StudentId.java new file mode 100644 index 0000000000..dad71275f7 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/StudentId.java @@ -0,0 +1,21 @@ +package io.micronaut.data.jdbc.h2.remap; + +import io.micronaut.core.convert.ConversionContext; +import io.micronaut.data.model.runtime.convert.AttributeConverter; +import jakarta.inject.Singleton; + +import java.util.UUID; + +record StudentId(UUID id) { +} + +@Singleton +class StudentIdAttributeConverter implements AttributeConverter { + public UUID convertToPersistedValue(StudentId entityValue, ConversionContext context) { + return entityValue.id(); + } + + public StudentId convertToEntityValue(UUID persistedValue, ConversionContext context) { + return new StudentId(persistedValue); + } +} diff --git a/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/StudentRepository.java b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/StudentRepository.java new file mode 100644 index 0000000000..f541ed1c31 --- /dev/null +++ b/data-jdbc/src/test/groovy/io/micronaut/data/jdbc/h2/remap/StudentRepository.java @@ -0,0 +1,9 @@ +package io.micronaut.data.jdbc.h2.remap; + +import io.micronaut.data.jdbc.annotation.JdbcRepository; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.repository.CrudRepository; + +@JdbcRepository(dialect = Dialect.H2) +interface StudentRepository extends CrudRepository { +} diff --git a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/DefaultMongoStoredQuery.java b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/DefaultMongoStoredQuery.java index ddcab0b62e..dda8e26a8c 100644 --- a/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/DefaultMongoStoredQuery.java +++ b/data-mongodb/src/main/java/io/micronaut/data/mongodb/operations/DefaultMongoStoredQuery.java @@ -70,7 +70,6 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; /** * Default implementation of {@link MongoStoredQuery}. diff --git a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java index 40474ff2df..8f1b835121 100644 --- a/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java +++ b/data-runtime/src/main/java/io/micronaut/data/runtime/operations/internal/sql/AbstractSqlRepositoryOperations.java @@ -18,6 +18,7 @@ import io.micronaut.aop.MethodInvocationContext; import io.micronaut.context.ApplicationContextProvider; import io.micronaut.context.BeanContext; +import io.micronaut.core.annotation.AnnotationClassValue; import io.micronaut.core.annotation.AnnotationMetadata; import io.micronaut.core.annotation.Internal; import io.micronaut.core.annotation.NonNull; @@ -26,6 +27,7 @@ import io.micronaut.data.annotation.AutoPopulated; import io.micronaut.data.annotation.MappedProperty; import io.micronaut.data.annotation.Repository; +import io.micronaut.data.annotation.TypeDef; import io.micronaut.data.exceptions.DataAccessException; import io.micronaut.data.model.Association; import io.micronaut.data.model.DataType; @@ -399,6 +401,16 @@ public JsonDataType getJsonDataType() { return property.getKey().getJsonDataType(); } + @Override + public Class getParameterConverterClass() { + return property.getKey() + .getAnnotationMetadata() + .getAnnotation(TypeDef.class) + .annotationClassValue("converter") + .flatMap(AnnotationClassValue::getType) + .orElse(null); + } + @Override public Object getValue() { return property.getValue(); @@ -427,6 +439,16 @@ public JsonDataType getJsonDataType() { public String[] getPropertyPath() { return pp.getArrayPath(); } + + @Override + public Class getParameterConverterClass() { + return pp.getProperty() + .getAnnotationMetadata() + .getAnnotation(TypeDef.class) + .annotationClassValue("converter") + .flatMap(AnnotationClassValue::getType) + .orElse(null); + } }); }