From 7556c844062faa719184005b2663e52f952bb44b Mon Sep 17 00:00:00 2001 From: Jonghyon Seo <31586979+shouwn@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:56:27 +0900 Subject: [PATCH] feat: support stream in spring data repository (#803) * feat: support stream in spring data repository * docs: support stream in spring data repository --- .../jpql-with-kotlin-jdsl/spring-supports.md | 30 ++- .../jpql-with-kotlin-jdsl/spring-supports.md | 30 ++- .../jpa/javax/jpql/select/SelectExample.kt | 58 +++++ .../data/jpa/jpql/select/SelectExample.kt | 51 ++++ .../repository/KotlinJdslJpqlExecutor.kt | 62 +++++ .../repository/KotlinJdslJpqlExecutorImpl.kt | 72 +++++- .../KotlinJdslJpqlExecutorImplTest.kt | 224 ++++++++++++++++++ .../jpa/repository/KotlinJdslJpqlExecutor.kt | 62 +++++ .../repository/KotlinJdslJpqlExecutorImpl.kt | 72 +++++- .../KotlinJdslJpqlExecutorImplTest.kt | 224 ++++++++++++++++++ 10 files changed, 849 insertions(+), 36 deletions(-) diff --git a/docs/en/jpql-with-kotlin-jdsl/spring-supports.md b/docs/en/jpql-with-kotlin-jdsl/spring-supports.md index bb6b60c43..3e63b848c 100644 --- a/docs/en/jpql-with-kotlin-jdsl/spring-supports.md +++ b/docs/en/jpql-with-kotlin-jdsl/spring-supports.md @@ -12,23 +12,33 @@ If you declare your `JpqlSerializer` or `JpqlIntrospector` as a bean, it will be If your `JpaRepository` extends `KotlinJdslJpqlExecutor`, you can use the extension provided by Kotlin JDSL. ```kotlin -interface AuthorRepository : JpaRepository, KotlinJdslJpqlExecutor interface BookRepository : JpaRepository, KotlinJdslJpqlExecutor -authorRepository.findAll { +val result: List = bookRepository.findAll { select( - path(Author::authorId), + path(Book::isbn), + ).from( + entity(Book::class), + ) +} + +val result: Page = bookRepository.findPage(pageable) { + select( + path(Book::isbn), ).from( - entity(Author::class), - join(BookAuthor::class).on(path(Author::authorId).equal(path(BookAuthor::authorId))), - ).groupBy( - path(Author::authorId), - ).orderBy( - count(Author::authorId).desc(), + entity(Book::class), + ) +} + +val result: Slice = bookRepository.findSlice(pageable) { + select( + path(Book::isbn), + ).from( + entity(Book::class), ) } -bookRepository.findPage(pageable) { +val result: Stream = bookRepository.findStream { select( path(Book::isbn), ).from( diff --git a/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md b/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md index b33b4930d..b32228d16 100644 --- a/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md +++ b/docs/ko/jpql-with-kotlin-jdsl/spring-supports.md @@ -12,23 +12,33 @@ Kotlin JDSL은 Spring Boot AutoConfigure를 지원합니다. 만약 사용하고 있는 `JpaRepository`가 `KotlinJdslJpqlExecutor`를 상속하면, Kotlin JDSL이 제공하는 확장 기능을 사용할 수 있습니다. ```kotlin -interface AuthorRepository : JpaRepository, KotlinJdslJpqlExecutor interface BookRepository : JpaRepository, KotlinJdslJpqlExecutor -authorRepository.findAll { +val result: List = bookRepository.findAll { select( - path(Author::authorId), + path(Book::isbn), + ).from( + entity(Book::class), + ) +} + +val result: Page = bookRepository.findPage(pageable) { + select( + path(Book::isbn), ).from( - entity(Author::class), - join(BookAuthor::class).on(path(Author::authorId).equal(path(BookAuthor::authorId))), - ).groupBy( - path(Author::authorId), - ).orderBy( - count(Author::authorId).desc(), + entity(Book::class), + ) +} + +val result: Slice = bookRepository.findSlice(pageable) { + select( + path(Book::isbn), + ).from( + entity(Book::class), ) } -bookRepository.findPage(pageable) { +val result: Stream = bookRepository.findStream { select( path(Book::isbn), ).from( diff --git a/example/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/javax/jpql/select/SelectExample.kt b/example/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/javax/jpql/select/SelectExample.kt index 5f30e16c0..4f494d8d9 100644 --- a/example/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/javax/jpql/select/SelectExample.kt +++ b/example/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/javax/jpql/select/SelectExample.kt @@ -18,6 +18,7 @@ import org.springframework.data.domain.PageRequest import org.springframework.data.domain.Sort import org.springframework.transaction.annotation.Transactional import java.time.OffsetDateTime +import java.util.stream.Collectors @Transactional @SpringBootTest @@ -89,6 +90,30 @@ class SelectExample : WithAssertions { assertThat(actual).isEqualTo(listOf(Isbn("04"), Isbn("05"), Isbn("06"))) } + @Test + fun `the stream of books`() { + // given + val pageable = PageRequest.of(1, 3, Sort.by(Sort.Direction.ASC, "isbn")) + + // when + val actual = bookRepository.findStream(pageable) { + select( + path(Book::isbn), + ).from( + entity(Book::class), + ) + } + + // then + assertThat(actual.collect(Collectors.toList())).isEqualTo( + listOf( + Isbn("04"), + Isbn("05"), + Isbn("06"), + ), + ) + } + @Test fun `the page of books`() { // given @@ -302,6 +327,39 @@ class SelectExample : WithAssertions { ) } + @Test + fun the_number_of_employees_per_department_stream() { + // given + data class Row( + val departmentId: Long, + val count: Long, + ) + + // when + val actual = employeeRepository.findStream { + selectNew( + path(EmployeeDepartment::departmentId), + count(Employee::employeeId), + ).from( + entity(Employee::class), + join(Employee::departments), + ).groupBy( + path(EmployeeDepartment::departmentId), + ).orderBy( + path(EmployeeDepartment::departmentId).asc(), + ) + } + + // then + assertThat(actual.collect(Collectors.toList())).isEqualTo( + listOf( + Row(1, 6), + Row(2, 15), + Row(3, 18), + ), + ) + } + @Test fun `the number of employees who belong to more than one department`() { // when diff --git a/example/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/jpql/select/SelectExample.kt b/example/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/jpql/select/SelectExample.kt index aaa6de0ed..61de2f806 100644 --- a/example/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/jpql/select/SelectExample.kt +++ b/example/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/example/spring/data/jpa/jpql/select/SelectExample.kt @@ -147,6 +147,24 @@ class SelectExample : WithAssertions { assertThat(actual.hasNext()).isTrue } + @Test + fun `the stream of books`() { + // given + val pageable = PageRequest.of(1, 3, Sort.by(Sort.Direction.ASC, "isbn")) + + // when + val actual = bookRepository.findStream(pageable) { + select( + path(Book::isbn), + ).from( + entity(Book::class), + ) + } + + // then + assertThat(actual.toList()).isEqualTo(listOf(Isbn("04"), Isbn("05"), Isbn("06"))) + } + @Test fun `the book with the most authors`() { // when @@ -321,6 +339,39 @@ class SelectExample : WithAssertions { ) } + @Test + fun the_number_of_employees_per_department_stream() { + // given + data class Row( + val departmentId: Long, + val count: Long, + ) + + // when + val actual = employeeRepository.findStream { + selectNew( + path(EmployeeDepartment::departmentId), + count(Employee::employeeId), + ).from( + entity(Employee::class), + join(Employee::departments), + ).groupBy( + path(EmployeeDepartment::departmentId), + ).orderBy( + path(EmployeeDepartment::departmentId).asc(), + ) + } + + // then + assertThat(actual.toList()).isEqualTo( + listOf( + Row(1, 6), + Row(2, 15), + Row(3, 18), + ), + ) + } + @Test fun `the number of employees who belong to more than one department`() { // given diff --git a/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutor.kt b/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutor.kt index 56a500830..91623f02d 100644 --- a/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutor.kt +++ b/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutor.kt @@ -11,6 +11,7 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.domain.Slice import org.springframework.data.repository.NoRepositoryBean +import java.util.stream.Stream @NoRepositoryBean @SinceJdsl("3.0.0") @@ -134,6 +135,67 @@ interface KotlinJdslJpqlExecutor { init: DSL.() -> JpqlQueryable>, ): Slice + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + offset: Int? = null, + limit: Int? = null, + init: Jpql.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: JpqlDsl.Constructor, + offset: Int? = null, + limit: Int? = null, + init: DSL.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: DSL, + offset: Int? = null, + limit: Int? = null, + init: DSL.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + pageable: Pageable, + init: Jpql.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: JpqlDsl.Constructor, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: DSL, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream + /** * Execute the update query. * diff --git a/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImpl.kt b/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImpl.kt index 59ddd4e00..93eb22f6e 100644 --- a/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImpl.kt +++ b/support/spring-data-jpa-javax/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImpl.kt @@ -22,6 +22,7 @@ import org.springframework.data.jpa.repository.support.QueryHints import org.springframework.data.repository.NoRepositoryBean import org.springframework.data.support.PageableExecutionUtilsAdaptor import org.springframework.transaction.annotation.Transactional +import java.util.stream.Stream import javax.persistence.EntityManager import javax.persistence.LockModeType import javax.persistence.Query @@ -60,10 +61,7 @@ open class KotlinJdslJpqlExecutorImpl( init: DSL.() -> JpqlQueryable>, ): List { val query: SelectQuery = jpql(dsl, init) - val jpaQuery = createJpaQuery(query, query.returnType).apply { - offset?.let { setFirstResult(it) } - limit?.let { setMaxResults(it) } - } + val jpaQuery = createJpaQuery(query, query.returnType, offset, limit) return jpaQuery.resultList } @@ -90,7 +88,7 @@ open class KotlinJdslJpqlExecutorImpl( ): List { val query: SelectQuery = jpql(dsl, init) - return createList(query, query.returnType, pageable) + return createSortedQuery(query, query.returnType, pageable).resultList } override fun findPage( @@ -143,6 +141,60 @@ open class KotlinJdslJpqlExecutorImpl( return createSlice(query, query.returnType, pageable) } + override fun findStream( + offset: Int?, + limit: Int?, + init: Jpql.() -> JpqlQueryable>, + ): Stream { + return findStream(Jpql, offset = offset, limit = limit, init) + } + + override fun findStream( + dsl: JpqlDsl.Constructor, + offset: Int?, + limit: Int?, + init: DSL.() -> JpqlQueryable>, + ): Stream { + return findStream(dsl.newInstance(), offset = offset, limit = limit, init) + } + + override fun findStream( + dsl: DSL, + offset: Int?, + limit: Int?, + init: DSL.() -> JpqlQueryable>, + ): Stream { + val query: SelectQuery = jpql(dsl, init) + val jpaQuery = createJpaQuery(query, query.returnType, offset, limit) + + return jpaQuery.resultStream + } + + override fun findStream( + pageable: Pageable, + init: Jpql.() -> JpqlQueryable>, + ): Stream { + return findStream(Jpql, pageable, init) + } + + override fun findStream( + dsl: JpqlDsl.Constructor, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream { + return findStream(dsl.newInstance(), pageable, init) + } + + override fun findStream( + dsl: DSL, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream { + val query: SelectQuery = jpql(dsl, init) + + return this.createSortedQuery(query, query.returnType, pageable).resultStream + } + @Transactional override fun update( init: Jpql.() -> JpqlQueryable>, @@ -198,9 +250,13 @@ open class KotlinJdslJpqlExecutorImpl( private fun createJpaQuery( query: JpqlQuery<*>, returnType: KClass, + offset: Int?, + limit: Int?, ): TypedQuery { return JpqlEntityManagerUtils.createQuery(entityManager, query, returnType, renderContext).apply { setMetadata(this, metadata) + offset?.let { setFirstResult(it) } + limit?.let { setMaxResults(it) } } } @@ -258,11 +314,11 @@ open class KotlinJdslJpqlExecutorImpl( } } - private fun createList( + private fun createSortedQuery( query: JpqlQuery<*>, returnType: KClass, pageable: Pageable, - ): List { + ): TypedQuery { val enhancedQuery = createJpaEnhancedQuery(query, returnType, pageable.sort) val sortedQuery = enhancedQuery.sortedQuery @@ -272,7 +328,7 @@ open class KotlinJdslJpqlExecutorImpl( sortedQuery.maxResults = pageable.pageSize } - return sortedQuery.resultList + return sortedQuery } private fun createPage( diff --git a/support/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImplTest.kt b/support/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImplTest.kt index 18f10efb5..96e040aff 100644 --- a/support/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImplTest.kt +++ b/support/spring-data-jpa-javax/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/javax/repository/KotlinJdslJpqlExecutorImplTest.kt @@ -29,6 +29,7 @@ import org.springframework.data.jpa.repository.support.QueryHints import org.springframework.data.support.PageableExecutionUtilsAdaptor import java.util.function.BiConsumer import java.util.function.LongSupplier +import java.util.stream.Stream import javax.persistence.EntityManager import javax.persistence.LockModeType import javax.persistence.Query @@ -181,6 +182,7 @@ class KotlinJdslJpqlExecutorImplTest : WithAssertions { private val pageable1 = PageRequest.of(1, 10, sort1) private val list1 = listOf("1", "2", "3") + private val stream1 = Stream.of("1", "2", "3") private val counts1 = listOf(1L, 1L, 1L) private val enhancedTypedQuery1: EnhancedTypedQuery by lazy(LazyThreadSafetyMode.NONE) { @@ -767,6 +769,228 @@ class KotlinJdslJpqlExecutorImplTest : WithAssertions { } } + @Test + fun findStream() { + // given + every { selectQuery1.returnType } returns String::class + every { + JpqlEntityManagerUtils.createQuery(any(), any>(), any>(), any()) + } returns stringTypedQuery1 + every { stringTypedQuery1.setLockMode(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setHint(any(), any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setFirstResult(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setMaxResults(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(offset1, limit1, createSelectQuery1) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery1.returnType + JpqlEntityManagerUtils.createQuery(entityManager, selectQuery1, String::class, renderContext) + metadata.lockModeType + stringTypedQuery1.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery1.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery1.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery1.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery1.setFirstResult(offset1) + stringTypedQuery1.setMaxResults(limit1) + stringTypedQuery1.resultStream + } + } + + @Test + fun `findStream() with a dsl`() { + // given + every { selectQuery2.returnType } returns String::class + every { + JpqlEntityManagerUtils.createQuery(any(), any>(), any>(), any()) + } returns stringTypedQuery2 + every { stringTypedQuery2.setLockMode(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setHint(any(), any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setFirstResult(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setMaxResults(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpql, offset1, limit1, createSelectQuery2) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery2.returnType + JpqlEntityManagerUtils.createQuery(entityManager, selectQuery2, String::class, renderContext) + metadata.lockModeType + stringTypedQuery2.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery2.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery2.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery2.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery2.setFirstResult(offset1) + stringTypedQuery2.setMaxResults(limit1) + stringTypedQuery2.resultStream + } + } + + @Test + fun `findStream() with a dsl object`() { + // given + every { selectQuery3.returnType } returns String::class + every { + JpqlEntityManagerUtils.createQuery(any(), any>(), any>(), any()) + } returns stringTypedQuery3 + every { stringTypedQuery3.setLockMode(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setHint(any(), any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setFirstResult(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setMaxResults(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpqlObject, offset1, limit1, createSelectQuery3) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery3.returnType + JpqlEntityManagerUtils.createQuery(entityManager, selectQuery3, String::class, renderContext) + metadata.lockModeType + stringTypedQuery3.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery3.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery3.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery3.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery3.setFirstResult(offset1) + stringTypedQuery3.setMaxResults(limit1) + stringTypedQuery3.resultStream + } + } + + @Test + fun `findStream() with a pageable`() { + // given + every { selectQuery1.returnType } returns String::class + every { + JpqlEntityManagerUtils.createEnhancedQuery( + any(), any>(), any>(), any(), any(), + ) + } returns enhancedTypedQuery1 + every { stringTypedQuery1.setLockMode(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setHint(any(), any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setFirstResult(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setMaxResults(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(pageable1, createSelectQuery1) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery1.returnType + JpqlEntityManagerUtils.createEnhancedQuery(entityManager, selectQuery1, String::class, sort1, renderContext) + metadata.lockModeType + stringTypedQuery1.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery1.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery1.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery1.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery1.firstResult = pageable1.offset.toInt() + stringTypedQuery1.maxResults = pageable1.pageSize + stringTypedQuery1.resultStream + } + } + + @Test + fun `findStream() with a dsl and a pageable`() { + // given + every { selectQuery2.returnType } returns String::class + every { + JpqlEntityManagerUtils.createEnhancedQuery( + any(), any>(), any>(), any(), any(), + ) + } returns enhancedTypedQuery2 + every { stringTypedQuery2.setLockMode(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setHint(any(), any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setFirstResult(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setMaxResults(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpql, pageable1, createSelectQuery2) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery2.returnType + JpqlEntityManagerUtils.createEnhancedQuery(entityManager, selectQuery2, String::class, sort1, renderContext) + metadata.lockModeType + stringTypedQuery2.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery2.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery2.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery2.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery2.firstResult = pageable1.offset.toInt() + stringTypedQuery2.maxResults = pageable1.pageSize + stringTypedQuery2.resultStream + } + } + + @Test + fun `findStream() with a dsl object and a pageable`() { + // given + every { selectQuery3.returnType } returns String::class + every { + JpqlEntityManagerUtils.createEnhancedQuery( + any(), any>(), any>(), any(), any(), + ) + } returns enhancedTypedQuery3 + every { stringTypedQuery3.setLockMode(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setHint(any(), any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setFirstResult(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setMaxResults(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpqlObject, pageable1, createSelectQuery3) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery3.returnType + JpqlEntityManagerUtils.createEnhancedQuery(entityManager, selectQuery3, String::class, sort1, renderContext) + metadata.lockModeType + stringTypedQuery3.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery3.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery3.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery3.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery3.firstResult = pageable1.offset.toInt() + stringTypedQuery3.maxResults = pageable1.pageSize + stringTypedQuery3.resultStream + } + } + @Test fun update() { // given diff --git a/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutor.kt b/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutor.kt index c3e28613c..a7002f28d 100644 --- a/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutor.kt +++ b/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutor.kt @@ -11,6 +11,7 @@ import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.domain.Slice import org.springframework.data.repository.NoRepositoryBean +import java.util.stream.Stream @NoRepositoryBean @SinceJdsl("3.0.0") @@ -134,6 +135,67 @@ interface KotlinJdslJpqlExecutor { init: DSL.() -> JpqlQueryable>, ): Slice + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + offset: Int? = null, + limit: Int? = null, + init: Jpql.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: JpqlDsl.Constructor, + offset: Int? = null, + limit: Int? = null, + init: DSL.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: DSL, + offset: Int? = null, + limit: Int? = null, + init: DSL.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + pageable: Pageable, + init: Jpql.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: JpqlDsl.Constructor, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream + + /** + * Returns all results of the select query. + */ + @SinceJdsl("3.5.4") + fun findStream( + dsl: DSL, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream + /** * Execute the update query. * diff --git a/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImpl.kt b/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImpl.kt index db74f6857..3d22667ff 100644 --- a/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImpl.kt +++ b/support/spring-data-jpa/src/main/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImpl.kt @@ -28,6 +28,7 @@ import org.springframework.data.jpa.repository.support.QueryHints import org.springframework.data.repository.NoRepositoryBean import org.springframework.data.support.PageableExecutionUtilsAdaptor import org.springframework.transaction.annotation.Transactional +import java.util.stream.Stream import kotlin.reflect.KClass @NoRepositoryBean @@ -62,10 +63,7 @@ open class KotlinJdslJpqlExecutorImpl( init: DSL.() -> JpqlQueryable>, ): List { val query: SelectQuery = jpql(dsl, init) - val jpaQuery = createJpaQuery(query, query.returnType).apply { - offset?.let { setFirstResult(it) } - limit?.let { setMaxResults(it) } - } + val jpaQuery = createJpaQuery(query, query.returnType, offset, limit) return jpaQuery.resultList } @@ -92,7 +90,7 @@ open class KotlinJdslJpqlExecutorImpl( ): List { val query: SelectQuery = jpql(dsl, init) - return createList(query, query.returnType, pageable) + return this.createSortedQuery(query, query.returnType, pageable).resultList } override fun findPage( @@ -145,6 +143,60 @@ open class KotlinJdslJpqlExecutorImpl( return createSlice(query, query.returnType, pageable) } + override fun findStream( + offset: Int?, + limit: Int?, + init: Jpql.() -> JpqlQueryable>, + ): Stream { + return findStream(Jpql, offset = offset, limit = limit, init) + } + + override fun findStream( + dsl: JpqlDsl.Constructor, + offset: Int?, + limit: Int?, + init: DSL.() -> JpqlQueryable>, + ): Stream { + return findStream(dsl.newInstance(), offset = offset, limit = limit, init) + } + + override fun findStream( + dsl: DSL, + offset: Int?, + limit: Int?, + init: DSL.() -> JpqlQueryable>, + ): Stream { + val query: SelectQuery = jpql(dsl, init) + val jpaQuery = createJpaQuery(query, query.returnType, offset, limit) + + return jpaQuery.resultStream + } + + override fun findStream( + pageable: Pageable, + init: Jpql.() -> JpqlQueryable>, + ): Stream { + return findStream(Jpql, pageable, init) + } + + override fun findStream( + dsl: JpqlDsl.Constructor, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream { + return findStream(dsl.newInstance(), pageable, init) + } + + override fun findStream( + dsl: DSL, + pageable: Pageable, + init: DSL.() -> JpqlQueryable>, + ): Stream { + val query: SelectQuery = jpql(dsl, init) + + return this.createSortedQuery(query, query.returnType, pageable).resultStream + } + @Transactional override fun update( init: Jpql.() -> JpqlQueryable>, @@ -200,9 +252,13 @@ open class KotlinJdslJpqlExecutorImpl( private fun createJpaQuery( query: JpqlQuery<*>, returnType: KClass, + offset: Int?, + limit: Int?, ): TypedQuery { return JpqlEntityManagerUtils.createQuery(entityManager, query, returnType, renderContext).apply { setMetadata(this, metadata) + offset?.let { setFirstResult(it) } + limit?.let { setMaxResults(it) } } } @@ -260,11 +316,11 @@ open class KotlinJdslJpqlExecutorImpl( } } - private fun createList( + private fun createSortedQuery( query: JpqlQuery<*>, returnType: KClass, pageable: Pageable, - ): List { + ): TypedQuery { val enhancedQuery = createJpaEnhancedQuery(query, returnType, pageable.sort) val sortedQuery = enhancedQuery.sortedQuery @@ -274,7 +330,7 @@ open class KotlinJdslJpqlExecutorImpl( sortedQuery.maxResults = pageable.pageSize } - return sortedQuery.resultList + return sortedQuery } private fun createPage( diff --git a/support/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImplTest.kt b/support/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImplTest.kt index 654d7e27b..52a4ae793 100644 --- a/support/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImplTest.kt +++ b/support/spring-data-jpa/src/test/kotlin/com/linecorp/kotlinjdsl/support/spring/data/jpa/repository/KotlinJdslJpqlExecutorImplTest.kt @@ -33,6 +33,7 @@ import org.springframework.data.jpa.repository.support.QueryHints import org.springframework.data.support.PageableExecutionUtilsAdaptor import java.util.function.BiConsumer import java.util.function.LongSupplier +import java.util.stream.Stream import kotlin.reflect.KClass @ExtendWith(MockKExtension::class) @@ -181,6 +182,7 @@ class KotlinJdslJpqlExecutorImplTest : WithAssertions { private val pageable1 = PageRequest.of(1, 10, sort1) private val list1 = listOf("1", "2", "3") + private val stream1 = Stream.of("1", "2", "3") private val counts1 = listOf(1L, 1L, 1L) private val enhancedTypedQuery1: EnhancedTypedQuery by lazy(LazyThreadSafetyMode.NONE) { @@ -767,6 +769,228 @@ class KotlinJdslJpqlExecutorImplTest : WithAssertions { } } + @Test + fun findStream() { + // given + every { selectQuery1.returnType } returns String::class + every { + JpqlEntityManagerUtils.createQuery(any(), any>(), any>(), any()) + } returns stringTypedQuery1 + every { stringTypedQuery1.setLockMode(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setHint(any(), any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setFirstResult(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setMaxResults(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(offset1, limit1, createSelectQuery1) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery1.returnType + JpqlEntityManagerUtils.createQuery(entityManager, selectQuery1, String::class, renderContext) + metadata.lockModeType + stringTypedQuery1.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery1.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery1.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery1.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery1.setFirstResult(offset1) + stringTypedQuery1.setMaxResults(limit1) + stringTypedQuery1.resultStream + } + } + + @Test + fun `findStream() with a dsl`() { + // given + every { selectQuery2.returnType } returns String::class + every { + JpqlEntityManagerUtils.createQuery(any(), any>(), any>(), any()) + } returns stringTypedQuery2 + every { stringTypedQuery2.setLockMode(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setHint(any(), any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setFirstResult(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setMaxResults(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpql, offset1, limit1, createSelectQuery2) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery2.returnType + JpqlEntityManagerUtils.createQuery(entityManager, selectQuery2, String::class, renderContext) + metadata.lockModeType + stringTypedQuery2.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery2.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery2.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery2.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery2.setFirstResult(offset1) + stringTypedQuery2.setMaxResults(limit1) + stringTypedQuery2.resultStream + } + } + + @Test + fun `findStream() with a dsl object`() { + // given + every { selectQuery3.returnType } returns String::class + every { + JpqlEntityManagerUtils.createQuery(any(), any>(), any>(), any()) + } returns stringTypedQuery3 + every { stringTypedQuery3.setLockMode(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setHint(any(), any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setFirstResult(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setMaxResults(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpqlObject, offset1, limit1, createSelectQuery3) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery3.returnType + JpqlEntityManagerUtils.createQuery(entityManager, selectQuery3, String::class, renderContext) + metadata.lockModeType + stringTypedQuery3.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery3.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery3.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery3.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery3.setFirstResult(offset1) + stringTypedQuery3.setMaxResults(limit1) + stringTypedQuery3.resultStream + } + } + + @Test + fun `findStream() with a pageable`() { + // given + every { selectQuery1.returnType } returns String::class + every { + JpqlEntityManagerUtils.createEnhancedQuery( + any(), any>(), any>(), any(), any(), + ) + } returns enhancedTypedQuery1 + every { stringTypedQuery1.setLockMode(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setHint(any(), any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setFirstResult(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.setMaxResults(any()) } returns stringTypedQuery1 + every { stringTypedQuery1.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(pageable1, createSelectQuery1) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery1.returnType + JpqlEntityManagerUtils.createEnhancedQuery(entityManager, selectQuery1, String::class, sort1, renderContext) + metadata.lockModeType + stringTypedQuery1.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery1.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery1.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery1.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery1.firstResult = pageable1.offset.toInt() + stringTypedQuery1.maxResults = pageable1.pageSize + stringTypedQuery1.resultStream + } + } + + @Test + fun `findStream() with a dsl and a pageable`() { + // given + every { selectQuery2.returnType } returns String::class + every { + JpqlEntityManagerUtils.createEnhancedQuery( + any(), any>(), any>(), any(), any(), + ) + } returns enhancedTypedQuery2 + every { stringTypedQuery2.setLockMode(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setHint(any(), any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setFirstResult(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.setMaxResults(any()) } returns stringTypedQuery2 + every { stringTypedQuery2.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpql, pageable1, createSelectQuery2) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery2.returnType + JpqlEntityManagerUtils.createEnhancedQuery(entityManager, selectQuery2, String::class, sort1, renderContext) + metadata.lockModeType + stringTypedQuery2.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery2.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery2.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery2.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery2.firstResult = pageable1.offset.toInt() + stringTypedQuery2.maxResults = pageable1.pageSize + stringTypedQuery2.resultStream + } + } + + @Test + fun `findStream() with a dsl object and a pageable`() { + // given + every { selectQuery3.returnType } returns String::class + every { + JpqlEntityManagerUtils.createEnhancedQuery( + any(), any>(), any>(), any(), any(), + ) + } returns enhancedTypedQuery3 + every { stringTypedQuery3.setLockMode(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setHint(any(), any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setFirstResult(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.setMaxResults(any()) } returns stringTypedQuery3 + every { stringTypedQuery3.resultStream } returns stream1 + every { metadata.lockModeType } returns lockModeType1 + every { metadata.queryHints } returns queryHints1 + + // when + val actual = sut.findStream(MyJpqlObject, pageable1, createSelectQuery3) + + // then + assertThat(actual).isEqualTo(stream1) + + verifySequence { + selectQuery3.returnType + JpqlEntityManagerUtils.createEnhancedQuery(entityManager, selectQuery3, String::class, sort1, renderContext) + metadata.lockModeType + stringTypedQuery3.setLockMode(lockModeType1) + metadata.queryHints + stringTypedQuery3.setHint(queryHint1.first, queryHint1.second) + stringTypedQuery3.setHint(queryHint2.first, queryHint2.second) + stringTypedQuery3.setHint(queryHint3.first, queryHint3.second) + stringTypedQuery3.firstResult = pageable1.offset.toInt() + stringTypedQuery3.maxResults = pageable1.pageSize + stringTypedQuery3.resultStream + } + } + @Test fun update() { // given