-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #171 from line/feature/jpa-reactive-3.0
feat: support JPA 3.0 Reactive
- Loading branch information
Showing
140 changed files
with
12,291 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
examples/spring-boot-hibernate-reactive-3/build.gradle.kts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
@Suppress("DSL_SCOPE_VIOLATION") | ||
plugins { | ||
alias(libs.plugins.spring.boot3) | ||
kotlin("plugin.spring") | ||
kotlin("plugin.jpa") | ||
} | ||
|
||
apply(plugin = "org.springframework.boot") | ||
apply(plugin = "kotlin-spring") | ||
apply(plugin = "kotlin-jpa") | ||
|
||
coverage { | ||
exclude(project) | ||
} | ||
|
||
dependencies { | ||
implementation(Modules.queryJakarta) | ||
// implementation("com.linecorp.kotlin-jdsl:spring-data-kotlin-jdsl-hibernate-reactive-jakarta:x.y.z") | ||
implementation(Modules.springDataHibernateReactiveJakarta) | ||
implementation(libs.hibernate.reactive.jakarta) | ||
implementation(libs.coroutine.jdk8) | ||
implementation(libs.coroutine.reactor) | ||
implementation(libs.bundles.mutiny) | ||
|
||
implementation(Modules.testFixtureHibernateReactiveJakarta) | ||
|
||
implementation(libs.spring.boot.webflux) | ||
implementation(libs.spring.boot3.jpa) | ||
implementation(libs.jackson.kotlin.module) | ||
implementation(libs.h2) | ||
implementation(platform(libs.spring.boot3.bom)) | ||
|
||
testImplementation(libs.spring.boot3.test) | ||
} |
78 changes: 78 additions & 0 deletions
78
...hibernate-reactive-3/src/main/kotlin/com/linecorp/kotlinjdsl/spring/data/example/Books.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.linecorp.kotlinjdsl.spring.data.example | ||
|
||
import com.linecorp.kotlinjdsl.querydsl.expression.col | ||
import com.linecorp.kotlinjdsl.spring.data.example.entity.Book | ||
import com.linecorp.kotlinjdsl.spring.data.reactive.query.SpringDataHibernateMutinyReactiveQueryFactory | ||
import com.linecorp.kotlinjdsl.spring.data.reactive.query.listQuery | ||
import com.linecorp.kotlinjdsl.spring.data.reactive.query.singleQuery | ||
import org.hibernate.reactive.mutiny.Mutiny | ||
import org.springframework.http.ResponseEntity | ||
import org.springframework.stereotype.Service | ||
import org.springframework.web.bind.annotation.* | ||
import reactor.core.publisher.Mono | ||
import java.util.concurrent.CompletionStage | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/books") | ||
class BookController( | ||
private val bookService: BookService, | ||
) { | ||
@PostMapping | ||
fun createBook(@RequestBody spec: BookService.CreateBookSpec): Mono<ResponseEntity<Long>> = | ||
Mono.fromCompletionStage { bookService.create(spec) }.map { ResponseEntity.ok().body(it.id) } | ||
|
||
@GetMapping("/{bookId}") | ||
suspend fun findById(@PathVariable bookId: Long): ResponseEntity<Book> { | ||
val book = bookService.findById(bookId) | ||
|
||
return ResponseEntity.ok(book) | ||
} | ||
|
||
@GetMapping | ||
suspend fun findAll(@RequestParam("name") name: String): ResponseEntity<List<Book>> { | ||
val books = bookService.findAll(BookService.FindBookSpec(name = name)) | ||
|
||
return ResponseEntity.ok(books) | ||
} | ||
} | ||
|
||
@Service | ||
class BookService( | ||
private val mutinySessionFactory: Mutiny.SessionFactory, | ||
private val queryFactory: SpringDataHibernateMutinyReactiveQueryFactory, | ||
) { | ||
fun create(spec: CreateBookSpec): CompletionStage<Book> { | ||
val book = Book(name = spec.name) | ||
return mutinySessionFactory.withSession { session -> session.persist(book).flatMap { session.flush() } } | ||
.map { book } | ||
.subscribeAsCompletionStage() | ||
} | ||
|
||
suspend fun findById(id: Long): Book { | ||
return queryFactory.singleQuery { | ||
select(entity(Book::class)) | ||
from(entity(Book::class)) | ||
where(col(Book::id).equal(id)) | ||
} | ||
} | ||
|
||
suspend fun findAll(spec: FindBookSpec): List<Book> { | ||
return queryFactory.listQuery { | ||
select(entity(Book::class)) | ||
from(entity(Book::class)) | ||
where(col(Book::name).like("%${spec.name}%")) | ||
} | ||
} | ||
|
||
data class CreateBookSpec( | ||
val name: String | ||
) { | ||
fun toJson() = """{"name":"$name"}""" | ||
} | ||
|
||
data class FindBookSpec( | ||
val name: String | ||
) { | ||
fun toJson() = """{"name":"$name"}""" | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
...-hibernate-reactive-3/src/main/kotlin/com/linecorp/kotlinjdsl/spring/data/example/Main.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.linecorp.kotlinjdsl.spring.data.example | ||
|
||
import org.springframework.boot.autoconfigure.SpringBootApplication | ||
import org.springframework.boot.runApplication | ||
|
||
@SpringBootApplication | ||
class ExampleApplication | ||
|
||
fun main(args: Array<String>) { | ||
runApplication<ExampleApplication>(*args) | ||
} |
43 changes: 43 additions & 0 deletions
43
.../src/main/kotlin/com/linecorp/kotlinjdsl/spring/data/example/QueryFactoryConfiguration.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.linecorp.kotlinjdsl.spring.data.example | ||
|
||
import com.linecorp.kotlinjdsl.query.creator.SubqueryCreatorImpl | ||
import com.linecorp.kotlinjdsl.spring.data.reactive.query.SpringDataHibernateMutinyReactiveQueryFactory | ||
import org.hibernate.reactive.mutiny.Mutiny.SessionFactory | ||
import org.springframework.context.annotation.Bean | ||
import org.springframework.context.annotation.Configuration | ||
import jakarta.persistence.EntityManagerFactory | ||
import jakarta.persistence.Persistence | ||
|
||
@Configuration | ||
class QueryFactoryConfiguration { | ||
@Bean | ||
fun entityManagerFactory(): EntityManagerFactory = Persistence.createEntityManagerFactory("book") | ||
|
||
@Bean | ||
fun mutinySessionFactory(entityManagerFactory: EntityManagerFactory): SessionFactory = | ||
entityManagerFactory.unwrap(SessionFactory::class.java) | ||
.apply { | ||
withSession { | ||
// currently H2 db does not support officially | ||
// and does not allow extract & create schema with h2 db in hibernate-reactive | ||
// so DDL query execute directly | ||
it.createNativeQuery<Int>( | ||
""" | ||
create table book ( | ||
id bigint not null auto_increment, | ||
name varchar(255), | ||
primary key (id) | ||
) | ||
""".trimIndent() | ||
).executeUpdate() | ||
}.subscribeAsCompletionStage().get() | ||
} | ||
|
||
@Bean | ||
fun queryFactory(sessionFactory: SessionFactory): SpringDataHibernateMutinyReactiveQueryFactory { | ||
return SpringDataHibernateMutinyReactiveQueryFactory( | ||
sessionFactory = sessionFactory, | ||
subqueryCreator = SubqueryCreatorImpl() | ||
) | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...ate-reactive-3/src/main/kotlin/com/linecorp/kotlinjdsl/spring/data/example/entity/Book.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.linecorp.kotlinjdsl.spring.data.example.entity | ||
|
||
import jakarta.persistence.* | ||
|
||
@Entity | ||
@Table(name = "book") | ||
data class Book( | ||
@Id | ||
@Column(name = "id") | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
val id: Long = 0, | ||
|
||
@Column | ||
val name: String, | ||
) { | ||
fun toJson() = """{"id":$id,"name":"$name"}""" | ||
} |
27 changes: 27 additions & 0 deletions
27
examples/spring-boot-hibernate-reactive-3/src/main/resources/META-INF/persistence.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence | ||
http://xmlns.jcp.org/xml/ns/persistence/persistence_3_1.xsd"> | ||
|
||
<persistence-unit name="book"> | ||
<provider>org.hibernate.reactive.provider.ReactivePersistenceProvider</provider> | ||
<class>com.linecorp.kotlinjdsl.spring.data.example.entity.Book</class> | ||
|
||
<properties> | ||
<property name="hibernate.vertx.pool.connect_timeout" value="30000"/> | ||
<property name="hibernate.vertx.pool.class" value="com.linecorp.kotlinjdsl.vertx.configuration.H2ConnectionPool"/> | ||
<property name="hibernate.vertx.pool.configuration_class" value="com.linecorp.kotlinjdsl.vertx.configuration.VertxH2DBConnectionPoolConfiguration"/> | ||
<property name="jakarta.persistence.jdbc.url" value="jdbc:h2:mem:~/test;MODE=MYSQL;DATABASE_TO_LOWER=TRUE;DATABASE_TO_UPPER=false"/> | ||
<property name="jakarta.persistence.jdbc.user" value="sa"/> | ||
<property name="jakarta.persistence.jdbc.password" value=""/> | ||
<property name="jakarta.persistence.jdbc.driver" value="org.h2.Driver"/> | ||
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect"/> | ||
<property name="hibernate.show_sql" value="true"/> | ||
<property name="hibernate.format_sql" value="true"/> | ||
<property name="hibernate.hbm2ddl.auto" value="none"/> | ||
<property name="hibernate.use_sql_comments" value="true"/> | ||
</properties> | ||
</persistence-unit> | ||
|
||
</persistence> |
58 changes: 58 additions & 0 deletions
58
.../test/kotlin/com/linecorp/kotlinjdsl/spring/data/example/BookControllerIntegrationTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.linecorp.kotlinjdsl.spring.data.example | ||
|
||
import com.linecorp.kotlinjdsl.spring.data.example.entity.Book | ||
import org.assertj.core.api.WithAssertions | ||
import org.junit.jupiter.api.Test | ||
import org.springframework.beans.factory.annotation.Autowired | ||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient | ||
import org.springframework.boot.test.context.SpringBootTest | ||
import org.springframework.test.web.reactive.server.WebTestClient | ||
|
||
@SpringBootTest | ||
@AutoConfigureWebTestClient(timeout = "100000") | ||
internal class BookControllerIntegrationTest : WithAssertions { | ||
@Autowired | ||
private lateinit var client: WebTestClient | ||
|
||
private val context = "/api/v1/books" | ||
|
||
@Test | ||
fun createBook() { | ||
createBook(BookService.CreateBookSpec("name")) | ||
} | ||
|
||
@Test | ||
fun findById() { | ||
val spec = BookService.CreateBookSpec("name1") | ||
val id = createBook(spec) | ||
client.get().uri("$context/$id") | ||
.exchange() | ||
.expectStatus().isOk | ||
.expectBody(Book::class.java) | ||
.value { | ||
assertThat(Book(id = id, name = spec.name)).isEqualTo(it) | ||
} | ||
|
||
} | ||
|
||
@Test | ||
fun findByName() { | ||
val spec1 = BookService.CreateBookSpec("name2") | ||
val spec2 = BookService.CreateBookSpec("name2") | ||
val id1 = createBook(spec1) | ||
val id2 = createBook(spec2) | ||
client.get().uri(context + "?name=${spec1.name}") | ||
.exchange() | ||
.expectStatus().isOk | ||
.expectBodyList(Book::class.java) | ||
.contains(Book(id = id1, name = spec1.name), Book(id = id2, name = spec2.name)) | ||
} | ||
|
||
private fun createBook(spec: BookService.CreateBookSpec) = client.post().uri(context) | ||
.bodyValue(spec) | ||
.exchange() | ||
.expectStatus().isOk | ||
.returnResult(Long::class.java) | ||
.responseBody | ||
.blockFirst()!! | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
apply<PublishPlugin>() | ||
|
||
coverage { | ||
exclude(project) | ||
} | ||
|
||
dependencies { | ||
api(Modules.reactiveCoreJakarta) | ||
|
||
compileOnly(libs.hibernate.reactive.jakarta) | ||
compileOnly(libs.slf4j) | ||
compileOnly(libs.bundles.mutiny) | ||
|
||
testImplementation(libs.bundles.mutiny) | ||
testImplementation(libs.coroutine.jdk8) | ||
testImplementation(Modules.testFixtureIntegrationReactiveJakarta) | ||
testImplementation(Modules.testFixtureHibernateReactiveJakarta) | ||
testImplementation(libs.hibernate.reactive.jakarta) | ||
testImplementation(libs.kotlin.reflect) | ||
testImplementation(libs.h2) | ||
testImplementation(libs.agroal.pool) | ||
testImplementation(libs.vertx.jdbc.client) | ||
} |
51 changes: 51 additions & 0 deletions
51
...ive-jakarta/src/main/kotlin/com/linecorp/kotlinjdsl/query/HibernateMutinyReactiveQuery.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.linecorp.kotlinjdsl.query | ||
|
||
import io.smallrye.mutiny.coroutines.awaitSuspending | ||
import org.hibernate.reactive.mutiny.Mutiny | ||
import jakarta.persistence.Parameter | ||
import kotlin.reflect.KClass | ||
|
||
class HibernateMutinyReactiveQuery<R>(private val query: Mutiny.Query<R>) : ReactiveQuery<R> { | ||
override suspend fun singleResult(): R = query.singleResult.awaitSuspending() | ||
override suspend fun resultList(): List<R> = query.resultList.awaitSuspending() | ||
override suspend fun singleResultOrNull(): R? = query.singleResultOrNull.awaitSuspending() | ||
override suspend fun executeUpdate(): Int = query.executeUpdate().awaitSuspending() | ||
|
||
override fun setParameter(position: Int, value: Any?): ReactiveQuery<R> { | ||
query.setParameter(position, value) | ||
return this | ||
} | ||
|
||
override fun setParameter(name: String, value: Any?): ReactiveQuery<R> { | ||
query.setParameter(name, value) | ||
return this | ||
} | ||
|
||
override fun <T> setParameter(parameter: Parameter<T>, value: T?): ReactiveQuery<R> { | ||
query.setParameter(parameter, value) | ||
return this | ||
} | ||
|
||
override fun setMaxResults(maxResults: Int): ReactiveQuery<R> { | ||
query.maxResults = maxResults | ||
return this | ||
} | ||
|
||
override fun setFirstResult(firstResult: Int): ReactiveQuery<R> { | ||
query.firstResult = firstResult | ||
return this | ||
} | ||
override fun setQueryHint(hintName: String, value: Any) { | ||
throw UnsupportedOperationException("Hibernate-reactive does not support JPA query hint yet. if hibernate-reactive setQueryHint method support officially please let us know. we will fix it") | ||
} | ||
|
||
override val maxResults: Int | ||
get() = query.maxResults | ||
override val firstResult: Int | ||
get() = query.firstResult | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
override fun <T : Any> unwrap(type: KClass<T>): T { | ||
return query as T | ||
} | ||
} |
Oops, something went wrong.