diff --git a/data-hibernate-jpa/src/test/groovy/io/micronaut/data/hibernate/AbstractHibernateQuerySpec.groovy b/data-hibernate-jpa/src/test/groovy/io/micronaut/data/hibernate/AbstractHibernateQuerySpec.groovy index 57c47d5c1b7..e7f3804556b 100644 --- a/data-hibernate-jpa/src/test/groovy/io/micronaut/data/hibernate/AbstractHibernateQuerySpec.groovy +++ b/data-hibernate-jpa/src/test/groovy/io/micronaut/data/hibernate/AbstractHibernateQuerySpec.groovy @@ -83,6 +83,8 @@ abstract class AbstractHibernateQuerySpec extends AbstractQuerySpec { def found = userWithWhereRepository.findById(e.id) then: found.isPresent() + cleanup: + userWithWhereRepository.deleteById(e.id) } void "test @where on find one deleted"() { @@ -729,6 +731,28 @@ abstract class AbstractHibernateQuerySpec extends AbstractQuerySpec { result.size() == 0 } + void "test order by embedded field"() { + when: + def e1 = userWithWhereRepository.save(new UserWithWhere(id: UUID.randomUUID(), email: "where1@somewhere.com", deleted: false)) + def u2 = new UserWithWhere(id: UUID.randomUUID(), email: "where2@somewhere.com", deleted: false) + u2.audit.createdTime = u2.audit.createdTime.plusSeconds(30) + def e2 = userWithWhereRepository.save(u2) + def found1 = userWithWhereRepository.findById(e1.id) + def found2 = userWithWhereRepository.findById(e2.id) + then: + found1.present + found2.present + when:"Sorted by embedded field works" + def sortedItems = userWithWhereRepository.findAllByIdInList(List.of(e1.id, e2.id), Sort.of(Sort.Order.desc("audit.createdTime", false))) + then: + sortedItems + sortedItems.size() == 2 + sortedItems[0].id == e2.id + sortedItems[1].id == e1.id + cleanup: + userWithWhereRepository.deleteAll(List.of(e1, e2)) + } + private static Specification testJoin(String value) { return ((root, query, criteriaBuilder) -> criteriaBuilder.equal(root.join("author").get("name"), value)) } diff --git a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/UserWithWhereRepository.java b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/UserWithWhereRepository.java index 61db4e03c3b..1832d0bec88 100644 --- a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/UserWithWhereRepository.java +++ b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/UserWithWhereRepository.java @@ -1,11 +1,16 @@ package io.micronaut.data.hibernate; +import io.micronaut.core.annotation.NonNull; import io.micronaut.data.annotation.Repository; import io.micronaut.data.hibernate.entities.UserWithWhere; +import io.micronaut.data.model.Sort; import io.micronaut.data.repository.CrudRepository; +import java.util.List; import java.util.UUID; @Repository public interface UserWithWhereRepository extends CrudRepository { + + List findAllByIdInList(List ids, @NonNull Sort sort); } diff --git a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/Audit.java b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/Audit.java new file mode 100644 index 00000000000..2cfb3ca8c11 --- /dev/null +++ b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/Audit.java @@ -0,0 +1,29 @@ +package io.micronaut.data.hibernate.entities; + +import io.micronaut.data.annotation.Embeddable; + +import java.time.Instant; + +@Embeddable +public class Audit { + + private Instant createdTime = Instant.now(); + + private String createdBy = "current"; + + public Instant getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(Instant createdTime) { + this.createdTime = createdTime; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } +} diff --git a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/UserWithWhere.java b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/UserWithWhere.java index 264805c6c84..0c3167d13ff 100644 --- a/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/UserWithWhere.java +++ b/data-hibernate-jpa/src/test/java/io/micronaut/data/hibernate/entities/UserWithWhere.java @@ -1,8 +1,10 @@ package io.micronaut.data.hibernate.entities; +import io.micronaut.data.annotation.Embeddable; import io.micronaut.data.annotation.Where; import jakarta.persistence.Column; +import jakarta.persistence.Embedded; import jakarta.persistence.Entity; import jakarta.persistence.Id; import jakarta.persistence.Table; @@ -17,6 +19,8 @@ public class UserWithWhere { private UUID id; private String email; private Boolean deleted; + @Embedded + private Audit audit = new Audit(); public UUID getId() { return id; @@ -41,4 +45,12 @@ public Boolean getDeleted() { public void setDeleted(Boolean deleted) { this.deleted = deleted; } + + public Audit getAudit() { + return audit; + } + + public void setAudit(Audit audit) { + this.audit = audit; + } } diff --git a/data-model/src/main/java/io/micronaut/data/model/query/builder/AbstractSqlLikeQueryBuilder.java b/data-model/src/main/java/io/micronaut/data/model/query/builder/AbstractSqlLikeQueryBuilder.java index 5b2ec2075b9..0852821fcdf 100644 --- a/data-model/src/main/java/io/micronaut/data/model/query/builder/AbstractSqlLikeQueryBuilder.java +++ b/data-model/src/main/java/io/micronaut/data/model/query/builder/AbstractSqlLikeQueryBuilder.java @@ -1928,7 +1928,8 @@ public QueryResult buildOrderBy(String query, @NonNull PersistentEntity entity, List associations = new ArrayList<>(path.getAssociations()); int assocCount = associations.size(); // If last association is embedded, it does not need to be joined to the alias since it will be in the destination table - if (assocCount > 0 && associations.get(assocCount - 1) instanceof Embedded) { + // JPA/Hibernate is special case and in that case we leave association for specific handling below + if (assocCount > 0 && computePropertyPaths() && associations.get(assocCount - 1) instanceof Embedded) { associations.remove(assocCount - 1); } if (associations.isEmpty()) { diff --git a/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy b/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy index 729d9e62020..b9d060ebad6 100644 --- a/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy +++ b/doc-examples/jdbc-spring-template-example-groovy/src/main/groovy/example/AbstractBookRepository.groovy @@ -18,15 +18,15 @@ import javax.sql.DataSource @JdbcRepository(dialect = Dialect.H2) abstract class AbstractBookRepository implements CrudRepository<@Valid Book, @NotNull Long> { - private final JdbcTemplate jdbcTemplate + private final JdbcTemplate jdbcTemplate; - AbstractBookRepository(JdbcTemplate jdbcTemplate) { // <1> - this.jdbcTemplate = jdbcTemplate + AbstractBookRepository(DataSource dataSource) { // <1> + this.jdbcTemplate = new JdbcTemplate(DelegatingDataSource.unwrapDataSource(dataSource)); //<2> } @Transactional List findByTitle(@NonNull @NotNull String title) { - return jdbcTemplate.queryForList('SELECT * FROM Book AS book WHERE book.title = ?', title) // <2> + return jdbcTemplate.queryForList('SELECT * FROM Book AS book WHERE book.title = ?', title) // <3> .collect(m -> new Book(m.id as Long, m.title as String, m.pages as Integer)) } } diff --git a/doc-examples/jdbc-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy b/doc-examples/jdbc-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy index fc853990a2c..454dd53c6f8 100644 --- a/doc-examples/jdbc-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy +++ b/doc-examples/jdbc-spring-template-example-groovy/src/test/groovy/example/BookRepositorySpec.groovy @@ -8,7 +8,7 @@ import spock.lang.Specification @MicronautTest(transactional = false) @Property(name = 'spec.name', value = 'BookRepositorySpec') @Property(name = 'datasources.default.name', value = 'mydb') -@Property(name = 'datasources.default.transaction-manager', value = 'springJdbc') +@Property(name = 'datasources.default.transactionManager', value = 'springJdbc') @Property(name = 'jpa.default.properties.hibernate.hbm2ddl.auto', value = 'create-drop') class BookRepositorySpec extends Specification { diff --git a/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java b/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java index ed62e95870a..f6b73bc1439 100644 --- a/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java +++ b/doc-examples/jdbc-spring-template-example-java/src/main/java/example/AbstractBookRepository.java @@ -2,6 +2,7 @@ import io.micronaut.context.annotation.Requires; import io.micronaut.core.annotation.NonNull; +import io.micronaut.data.connection.jdbc.advice.DelegatingDataSource; import io.micronaut.data.jdbc.annotation.JdbcRepository; import io.micronaut.data.model.query.builder.sql.Dialect; import io.micronaut.data.repository.CrudRepository; @@ -20,13 +21,13 @@ public abstract class AbstractBookRepository implements CrudRepository<@Valid Bo private final JdbcTemplate jdbcTemplate; - public AbstractBookRepository(JdbcTemplate jdbcTemplate) { // <1> - this.jdbcTemplate = jdbcTemplate; + public AbstractBookRepository(DataSource dataSource) { // <1> + this.jdbcTemplate = new JdbcTemplate(DelegatingDataSource.unwrapDataSource(dataSource)); //<2> } @Transactional public List findByTitle(@NonNull @NotNull String title) { - return jdbcTemplate.queryForList("SELECT * FROM Book AS book WHERE book.title = ?", title) // <2> + return jdbcTemplate.queryForList("SELECT * FROM Book AS book WHERE book.title = ?", title) // <3> .stream() .map(m -> new Book((Long) m.get("id"), (String) m.get("title"), (Integer) m.get("pages"))) .toList(); diff --git a/doc-examples/jdbc-spring-template-example-java/src/test/java/example/BookRepositoryTest.java b/doc-examples/jdbc-spring-template-example-java/src/test/java/example/BookRepositoryTest.java index 0d17c7eec2e..a1484a5c668 100644 --- a/doc-examples/jdbc-spring-template-example-java/src/test/java/example/BookRepositoryTest.java +++ b/doc-examples/jdbc-spring-template-example-java/src/test/java/example/BookRepositoryTest.java @@ -15,7 +15,7 @@ @MicronautTest @Property(name = "spec.name", value = "BookRepositoryTest") @Property(name = "datasources.default.name", value = "mydb") -@Property(name = "datasources.default.transaction-manager", value = "springJdbc") +@Property(name = "datasources.default.transactionManager", value = "springJdbc") @Property(name = "jpa.default.properties.hibernate.hbm2ddl.auto", value = "create-drop") class BookRepositoryTest { diff --git a/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt b/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt index 4ff7c125271..fb0f133f584 100644 --- a/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt +++ b/doc-examples/jdbc-spring-template-example-kotlin/src/main/kotlin/example/AbstractBookRepository.kt @@ -13,11 +13,13 @@ import org.springframework.jdbc.core.JdbcTemplate @Requires(property = "spec.name", value = "BookRepositoryTest") // tag::clazz[] // tag::clazz[] @JdbcRepository(dialect = Dialect.H2) -abstract class AbstractBookRepository(val jdbcTemplate: JdbcTemplate) : CrudRepository<@Valid Book, Long> { //<1> +abstract class AbstractBookRepository(dataSource: DataSource) : CrudRepository<@Valid Book, Long> { // <1> + + private val jdbcTemplate: JdbcTemplate = JdbcTemplate(DelegatingDataSource.unwrapDataSource(dataSource)) //<2> @Transactional open fun findByTitle(title: String) = jdbcTemplate - .queryForList("SELECT * FROM Book AS book WHERE book.title = ?", title) // <2> + .queryForList("SELECT * FROM Book AS book WHERE book.title = ?", title) // <3> .map { m -> Book(m["id"] as Long, m["title"] as String, m["pages"] as Int) } } // end::clazz[] diff --git a/doc-examples/jdbc-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt b/doc-examples/jdbc-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt index ba6ba922653..ef38d7c1299 100644 --- a/doc-examples/jdbc-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt +++ b/doc-examples/jdbc-spring-template-example-kotlin/src/test/kotlin/example/BookRepositoryTest.kt @@ -11,7 +11,7 @@ import org.junit.jupiter.api.Test @MicronautTest @Property(name = "spec.name", value = "BookRepositoryTest") @Property(name = "datasources.default.name", value = "mydb") -@Property(name = "datasources.default.transaction-manager", value = "springJdbc") +@Property(name = "datasources.default.transactionManager", value = "springJdbc") @Property(name = "jpa.default.properties.hibernate.hbm2ddl.auto", value = "create-drop") internal class BookRepositoryTest { diff --git a/src/main/docs/guide/dbc/dbcRepositories.adoc b/src/main/docs/guide/dbc/dbcRepositories.adoc index 4888d6d6da7..f38cc5cb6f8 100644 --- a/src/main/docs/guide/dbc/dbcRepositories.adoc +++ b/src/main/docs/guide/dbc/dbcRepositories.adoc @@ -46,8 +46,9 @@ The following code illustrates an example that integrates a `JdbcTemplate` insta snippet::example.AbstractBookRepository[project-base="doc-examples/jdbc-spring-template-example",source="main",tags="clazz"] -<1> Inject the `org.springframework.jdbc.core.JdbcTemplate` configured by the application. -<2> Now the `JdbcTemplate` API can be used to implement repository methods. +<1> Inject the `java.sql.DataSource` configured by the application. +<2> Instantiate a `JdbcTemplate` object using the injected `DataSource`. +<3> Now the `JdbcTemplate` API can be used to implement repository methods. In addition, the transaction manager for Spring JDBC needs to be set in application configuration.