Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for creating queries via initialized JpqlDsl object #642

Merged
merged 3 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions docs/en/jpql-with-kotlin-jdsl/custom-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ You can use them to create your own functions.
You can also create your own `Model` that implements [`Expression`](expressions.md) or [`Predicate`](predicates.md) and create a function to return this Model.
You can implement [`JpqlSerializer`](custom-dsl.md#serializer) to let Kotlin JDSL to render `Model` to String.

{% hint style="info" %}
You need to implement `JpqlDsl.Constructor` as a companion object so that `jpql()` can recognize the DSL.
{% endhint %}
There are two ways to pass your own DSL to `jpql()`.
The first is to implement `JpqlDsl.Constructor` as a companion object to create a DSL object, and the second is to create a DSL instance.

### JpqlDsl.Constructor

With this way, you don't need to create an instance and a new instance is automatically created for each query creation.

```kotlin
class MyJpql : Jpql() {
Expand Down Expand Up @@ -39,6 +42,34 @@ val query = jpql(MyJpql) {
}
```

### Jpql Instance

With this way, you can reuse a single instance for query creation and utilize dependency injection.

```kotlin
class MyJpql(
private val encryptor: Encryptor,
) : Jpql() {
fun Path<String>.equalToEncrypted(value: String): Predicate {
val encrypted = encryptor.encrypt(value)
return this.eq(encrypted)
}
}

val encryptor = Encryptor()
val instance = MyJpql(encryptor)

val query = jpql(instance) {
select(
entity(Book::class)
).from(
entity(Book::class)
).where(
path(Book::title).equalToEncrypted("plain")
)
}
```

### Serializer

Implement `JpqlSerializer` and add it to `RenderContext` to render your own `Model` to String.
Expand Down
39 changes: 35 additions & 4 deletions docs/ko/jpql-with-kotlin-jdsl/custom-dsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
그리고 [`Expression`](expressions.md) 혹은 [`Predicate`](predicates.md)를 구현한 나만의 `Model` 클래스를 만들고, 이를 반환하는 함수를 만들 수 있습니다.
이 경우 [`JpqlSerializer`](custom-dsl.md#serializer)를 구현하여 `Model`을 String으로 랜더링하는 방법을 Kotlin JDSL에게 알려줄 수 있습니다.

{% hint style="info" %}
`jpql()`이 DSL을 인식하기 위해서 `JpqlDsl.Constructor`를 companion object로 구현해야 합니다.
{% endhint %}
이렇게 만들어진 나만의 DSL을 `jpql()`에 전달하기 위한 두 가지 방법이 있습니다.
첫 번째는 DSL 객체를 생성하는 `JpqlDsl.Constructor`를 companion object로 구현하는 것이고, 두 번째는 DSL 인스턴스를 생성하는 것입니다.

### JpqlDsl.Constructor

이 방법을 사용하면 클래스만 선언해 사용할 수 있으며 쿼리 생성 시마다 새로운 인스턴스를 자동으로 생성합니다.

```kotlin
class MyJpql : Jpql() {
Expand Down Expand Up @@ -39,9 +42,37 @@ val query = jpql(MyJpql) {
}
```

### Jpql Instance

이 방법을 사용하면 쿼리 생성에 하나의 인스턴스를 재활용할 수 있으며 의존성 주입을 활용할 수 있습니다.

```kotlin
class MyJpql(
private val encryptor: Encryptor,
) : Jpql() {
fun Path<String>.equalToEncrypted(value: String): Predicate {
val encrypted = encryptor.encrypt(value)
return this.eq(encrypted)
}
}

val encryptor = Encryptor()
val instance = MyJpql(encryptor)

val query = jpql(instance) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jpql 객체 생성과 query의 생성 문맥을 분리하기 위해 다음과 같이 개행이 있으면 좋을 것 같아요.

val encryptor = Encryptor()
val instance = MyJpql(encryptor)

val query = jpql(instance) {
// ... query
}

select(
entity(Book::class)
).from(
entity(Book::class)
).where(
path(Book::title).equalToEncrypted("plain")
)
}
```

### Serializer

나만의 `Model`을 String을 랜더링하기 위해 `JpqlSerializer`를 구현하고 이를 `RenderContext`에 추가해야 합니다.
나만의 `Model`을 String으로 랜더링하기 위해 `JpqlSerializer`를 구현하고 이를 `RenderContext`에 추가해야 합니다.

`JpqlSerializer`는 랜더링 로직을 구현할 수 있도록 `JpqlWriter`와 `RenderContext`를 제공합니다.
`RenderContext`를 통해 `JpqlRenderSerializer`를 얻어 나의 `Model`이 가지고 있는 다른 `Model`을 랜더링할 수 있습니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ inline fun <DSL : JpqlDsl, Q : JpqlQuery<Q>> jpql(dsl: JpqlDsl.Constructor<DSL>,
return dsl.newInstance().init().toQuery()
}

/**
* Builds new JPQL query using provided JpqlDsl instance.
*/
@SinceJdsl("3.4.0")
inline fun <DSL : JpqlDsl, Q : JpqlQuery<Q>> jpql(jpql: DSL, init: DSL.() -> JpqlQueryable<Q>): Q {
return jpql.init().toQuery()
}

/**
* Default implementation of DSL for building a JPQL query.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ class KotlinJdslQueryProviderFactory(
return KotlinJdslQueryProvider(query, queryParams, context)
}

/**
* Creates a [KotlinJdslQueryProvider] from a select query.
*/
fun <DSL : JpqlDsl, T : Any> create(
dsl: DSL,
init: DSL.() -> JpqlQueryable<SelectQuery<T>>,
): KotlinJdslQueryProvider<T> {
val query = jpql(dsl, init)

return KotlinJdslQueryProvider(query, emptyMap(), context)
}

/**
* Creates a [KotlinJdslQueryProvider] from a select query.
*/
fun <DSL : JpqlDsl, T : Any> create(
dsl: DSL,
queryParams: Map<String, Any?>,
init: DSL.() -> JpqlQueryable<SelectQuery<T>>,
): KotlinJdslQueryProvider<T> {
val query = jpql(dsl, init)

return KotlinJdslQueryProvider(query, queryParams, context)
}

/**
* Creates a [KotlinJdslQueryProvider] from a select query.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ class KotlinJdslQueryProviderFactoryTest : WithAssertions {
@MockK
private lateinit var createSelectQuery2: MyJpql.() -> JpqlQueryable<SelectQuery<String>>

@MockK
private lateinit var createSelectQuery3: MyJpqlObject.() -> JpqlQueryable<SelectQuery<String>>

@MockK
private lateinit var selectQuery1: SelectQuery<String>

@MockK
private lateinit var selectQuery2: SelectQuery<String>

@MockK
private lateinit var selectQuery3: SelectQuery<String>

private val queryParams1 = mapOf("authorId" to 1L)

private class MyJpql : Jpql() {
Expand All @@ -53,18 +59,33 @@ class KotlinJdslQueryProviderFactoryTest : WithAssertions {
}
}

private object MyJpqlObject : Jpql() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
return javaClass == other?.javaClass
}

override fun hashCode(): Int {
return javaClass.hashCode()
}
}

@BeforeEach
fun setUp() {
every { createSelectQuery1.invoke(any()) } returns selectQuery1
every { createSelectQuery2.invoke(any()) } returns selectQuery2
every { createSelectQuery3.invoke(any()) } returns selectQuery3

every { selectQuery1.toQuery() } returns selectQuery1
every { selectQuery2.toQuery() } returns selectQuery2
every { selectQuery3.toQuery() } returns selectQuery3

excludeRecords { createSelectQuery1.invoke(any()) }
excludeRecords { createSelectQuery2.invoke(any()) }
excludeRecords { createSelectQuery3.invoke(any()) }
excludeRecords { selectQuery1.toQuery() }
excludeRecords { selectQuery2.toQuery() }
excludeRecords { selectQuery3.toQuery() }
}

@Test
Expand Down Expand Up @@ -127,6 +148,36 @@ class KotlinJdslQueryProviderFactoryTest : WithAssertions {
assertThat(actual).isEqualTo(expected)
}

@Test
fun `create() with an dsl object and a select queryable`() {
// when
val actual = sut.create(MyJpqlObject, createSelectQuery3)

// then
val expected = KotlinJdslQueryProvider(
query = jpql(MyJpqlObject, createSelectQuery3),
queryParams = emptyMap(),
context = context,
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun `create() with an dsl object, a select queryable, and query params`() {
// when
val actual = sut.create(MyJpqlObject, queryParams1, createSelectQuery3)

// then
val expected = KotlinJdslQueryProvider(
query = jpql(MyJpqlObject, createSelectQuery3),
queryParams = queryParams1,
context = context,
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun `create() with a select query`() {
// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,31 @@ class KotlinJdslQueryProviderFactory(
return KotlinJdslQueryProvider(query, queryParams, context)
}

/**
* Creates a [KotlinJdslQueryProvider] from a select query.
*/
fun <DSL : JpqlDsl, T : Any> create(
dsl: DSL,
init: DSL.() -> JpqlQueryable<SelectQuery<T>>,
): KotlinJdslQueryProvider<T> {
val query = jpql(dsl, init)

return KotlinJdslQueryProvider(query, emptyMap(), context)
}

/**
* Creates a [KotlinJdslQueryProvider] from a select query.
*/
fun <DSL : JpqlDsl, T : Any> create(
dsl: DSL,
queryParams: Map<String, Any?>,
init: DSL.() -> JpqlQueryable<SelectQuery<T>>,
): KotlinJdslQueryProvider<T> {
val query = jpql(dsl, init)

return KotlinJdslQueryProvider(query, queryParams, context)
}

/**
* Creates a [KotlinJdslQueryProvider] from a select query.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,18 @@ class KotlinJdslQueryProviderFactoryTest : WithAssertions {
@MockK
private lateinit var createSelectQuery2: MyJpql.() -> JpqlQueryable<SelectQuery<String>>

@MockK
private lateinit var createSelectQuery3: MyJpqlObject.() -> JpqlQueryable<SelectQuery<String>>

@MockK
private lateinit var selectQuery1: SelectQuery<String>

@MockK
private lateinit var selectQuery2: SelectQuery<String>

@MockK
private lateinit var selectQuery3: SelectQuery<String>

private val queryParams1 = mapOf("authorId" to 1L)

private class MyJpql : Jpql() {
Expand All @@ -53,18 +59,33 @@ class KotlinJdslQueryProviderFactoryTest : WithAssertions {
}
}

private object MyJpqlObject : Jpql() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
return javaClass == other?.javaClass
}

override fun hashCode(): Int {
return javaClass.hashCode()
}
}

@BeforeEach
fun setUp() {
every { createSelectQuery1.invoke(any()) } returns selectQuery1
every { createSelectQuery2.invoke(any()) } returns selectQuery2
every { createSelectQuery3.invoke(any()) } returns selectQuery3

every { selectQuery1.toQuery() } returns selectQuery1
every { selectQuery2.toQuery() } returns selectQuery2
every { selectQuery3.toQuery() } returns selectQuery3

excludeRecords { createSelectQuery1.invoke(any()) }
excludeRecords { createSelectQuery2.invoke(any()) }
excludeRecords { createSelectQuery3.invoke(any()) }
excludeRecords { selectQuery1.toQuery() }
excludeRecords { selectQuery2.toQuery() }
excludeRecords { selectQuery3.toQuery() }
}

@Test
Expand Down Expand Up @@ -127,6 +148,36 @@ class KotlinJdslQueryProviderFactoryTest : WithAssertions {
assertThat(actual).isEqualTo(expected)
}

@Test
fun `create() with an dsl object and a select queryable`() {
// when
val actual = sut.create(MyJpqlObject, createSelectQuery3)

// then
val expected = KotlinJdslQueryProvider(
query = jpql(MyJpqlObject, createSelectQuery3),
queryParams = emptyMap(),
context = context,
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun `create() with an dsl object, a select queryable, and query params`() {
// when
val actual = sut.create(MyJpqlObject, queryParams1, createSelectQuery3)

// then
val expected = KotlinJdslQueryProvider(
query = jpql(MyJpqlObject, createSelectQuery3),
queryParams = queryParams1,
context = context,
)

assertThat(actual).isEqualTo(expected)
}

@Test
fun `create() with a select query`() {
// when
Expand Down
Loading