Skip to content

Commit

Permalink
Support Criteria Delete API (#38)
Browse files Browse the repository at this point in the history
* Support Criteria Delete API
implement missing updateQuery in SpringDataQueryFactory
  • Loading branch information
Hyunsik Kang authored Feb 11, 2022
1 parent 5b67bd4 commit 2612fec
Show file tree
Hide file tree
Showing 102 changed files with 2,345 additions and 357 deletions.
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ val books: List<Row> = queryFactory.listQuery {
}
```

### Update
### Update & Delete

Users can perform bulk update through update query.
* kotlin-jdsl's update does not require from clause. Type T given as generic handles from automatically.
* According to the JPA specification, update does not support join, fetch, group by, order by, limit.
Users can perform bulk update/delete through update/delete query.
* kotlin-jdsl's update/delete does not require from clause. Type T given as generic handles from automatically.
* According to the JPA specification, update/delete does not support join, fetch, group by, order by, limit.
* If you want to use an association mapping as a where condition, you must use [associate](#associate).

```kotlin
Expand All @@ -103,9 +103,16 @@ val query: Query = queryFactory.updateQuery<Order> {
setParams(col(Order::purchaserId) to 3000)
}

val udpatedRowsCount: Int = query.executeUpdate()
val updatedRowsCount: Int = query.executeUpdate()

val deleteQuery: Query = queryFactory.deleteQuery<Order> {
where(col(Order::purchaserId).`in`(1000, 2000))
}

val deletedRowsCount: Int = deleteQuery.executeUpdate()
```


### Expression

Kotlin JDSL supports various expressions.
Expand Down Expand Up @@ -226,12 +233,17 @@ val query = queryFactory.selectQuery<String> {
associate(OrderAddress::class, Address::class, on(OrderAddress::address))
}

queryFactory.updateQuery<OrderAddress> {
val updatedRowCount = queryFactory.updateQuery<OrderAddress> {
where(col(OrderAddress::id).equal(address1.id))
associate(OrderAddress::class, Address::class, on(OrderAddress::address))
set(col(Address::zipCode), "test")
set(col(Address::baseAddress), "base")
}.executeUpdate()

val deletedRowCount = queryFactory.deleteQuery<OrderAddress> {
where(col(OrderAddress::id).equal(address1.id))
associate(OrderAddress::class, Address::class, on(OrderAddress::address))
}.executeUpdate()
```


Expand Down
2 changes: 2 additions & 0 deletions core/src/main/kotlin/com/linecorp/kotlinjdsl/QueryFactory.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.linecorp.kotlinjdsl

import com.linecorp.kotlinjdsl.query.spec.expression.SubqueryExpressionSpec
import com.linecorp.kotlinjdsl.querydsl.CriteriaDeleteQueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaQueryDsl
import com.linecorp.kotlinjdsl.querydsl.SubqueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaUpdateQueryDsl
Expand All @@ -13,5 +14,6 @@ interface QueryFactory {
fun <T> typedQuery(returnType: Class<T>, dsl: CriteriaQueryDsl<T>.() -> Unit) = selectQuery(returnType, dsl)
fun <T> selectQuery(returnType: Class<T>, dsl: CriteriaQueryDsl<T>.() -> Unit): TypedQuery<T>
fun <T: Any> updateQuery(target: KClass<T>, dsl: CriteriaUpdateQueryDsl.() -> Unit): Query
fun <T: Any> deleteQuery(target: KClass<T>, dsl: CriteriaDeleteQueryDsl.() -> Unit): Query
fun <T> subquery(returnType: Class<T>, dsl: SubqueryDsl<T>.() -> Unit): SubqueryExpressionSpec<T>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linecorp.kotlinjdsl

import com.linecorp.kotlinjdsl.querydsl.CriteriaDeleteQueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaQueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaUpdateQueryDsl
import com.linecorp.kotlinjdsl.querydsl.SubqueryDsl
Expand Down Expand Up @@ -27,5 +28,8 @@ inline fun <reified T> QueryFactory.selectQuery(noinline dsl: CriteriaQueryDsl<T
inline fun <reified T: Any> QueryFactory.updateQuery(noinline dsl: CriteriaUpdateQueryDsl.() -> Unit) =
updateQuery(T::class, dsl)

inline fun <reified T: Any> QueryFactory.deleteQuery(noinline dsl: CriteriaDeleteQueryDsl.() -> Unit) =
deleteQuery(T::class, dsl)

inline fun <reified T> QueryFactory.subquery(noinline dsl: SubqueryDsl<T>.() -> Unit) =
subquery(T::class.java, dsl)
11 changes: 7 additions & 4 deletions core/src/main/kotlin/com/linecorp/kotlinjdsl/QueryFactoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ package com.linecorp.kotlinjdsl
import com.linecorp.kotlinjdsl.query.creator.CriteriaQueryCreator
import com.linecorp.kotlinjdsl.query.creator.SubqueryCreator
import com.linecorp.kotlinjdsl.query.spec.expression.SubqueryExpressionSpec
import com.linecorp.kotlinjdsl.querydsl.CriteriaQueryDsl
import com.linecorp.kotlinjdsl.querydsl.QueryDslImpl
import com.linecorp.kotlinjdsl.querydsl.SubqueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaUpdateQueryDsl
import com.linecorp.kotlinjdsl.querydsl.*
import javax.persistence.Query
import javax.persistence.TypedQuery
import kotlin.reflect.KClass
Expand All @@ -30,6 +27,12 @@ class QueryFactoryImpl(
)
}

override fun <T: Any> deleteQuery(target: KClass<T>, dsl: CriteriaDeleteQueryDsl.() -> Unit): Query {
return criteriaQueryCreator.createQuery(
QueryDslImpl(target.java).apply(dsl).apply { from(target) }.createCriteriaDeleteQuerySpec()
)
}

override fun <T> subquery(
returnType: Class<T>,
dsl: SubqueryDsl<T>.() -> Unit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linecorp.kotlinjdsl.query.creator

import com.linecorp.kotlinjdsl.query.CriteriaDeleteQuerySpec
import com.linecorp.kotlinjdsl.query.CriteriaQuerySpec
import com.linecorp.kotlinjdsl.query.CriteriaUpdateQuerySpec
import javax.persistence.Query
Expand All @@ -8,4 +9,5 @@ import javax.persistence.TypedQuery
interface CriteriaQueryCreator {
fun <T> createQuery(spec: CriteriaQuerySpec<T>): TypedQuery<T>
fun <T> createQuery(spec: CriteriaUpdateQuerySpec<T>): Query
fun <T> createQuery(spec: CriteriaDeleteQuerySpec<T>): Query
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.linecorp.kotlinjdsl.query.creator

import com.linecorp.kotlinjdsl.query.CriteriaDeleteQuerySpec
import com.linecorp.kotlinjdsl.query.CriteriaQuerySpec
import com.linecorp.kotlinjdsl.query.CriteriaUpdateQuerySpec
import javax.persistence.EntityManager
import javax.persistence.Query
import javax.persistence.TypedQuery
import javax.persistence.criteria.CriteriaDelete
import javax.persistence.criteria.CriteriaUpdate

class CriteriaQueryCreatorImpl(
Expand Down Expand Up @@ -43,4 +45,17 @@ class CriteriaQueryCreatorImpl(
}
}

@Suppress("UNCHECKED_CAST")
override fun <T> createQuery(spec: CriteriaDeleteQuerySpec<T>): Query {
val criteriaBuilder = em.criteriaBuilder
val query = criteriaBuilder.createCriteriaDelete(spec.targetEntity) as CriteriaDelete<Any>
val froms = spec.from.associate(spec.associate, query, spec.targetEntity)

spec.where.apply(froms, query, criteriaBuilder)

return em.createQuery(query).apply {
spec.jpaHint.apply(this)
spec.sqlHint.apply(this)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.linecorp.kotlinjdsl

import com.linecorp.kotlinjdsl.query.spec.expression.SubqueryExpressionSpec
import com.linecorp.kotlinjdsl.querydsl.CriteriaDeleteQueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaQueryDsl
import com.linecorp.kotlinjdsl.querydsl.CriteriaUpdateQueryDsl
import com.linecorp.kotlinjdsl.querydsl.SubqueryDsl
import com.linecorp.kotlinjdsl.querydsl.expression.col
import com.linecorp.kotlinjdsl.test.WithKotlinJdslAssertions
import io.mockk.confirmVerified
import io.mockk.every
Expand All @@ -12,6 +15,7 @@ import io.mockk.verify
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import java.util.stream.Stream
import javax.persistence.Query
import javax.persistence.TypedQuery

@ExtendWith(MockKExtension::class)
Expand Down Expand Up @@ -125,6 +129,52 @@ internal class QueryFactoryExtensionsTest : WithKotlinJdslAssertions {
confirmVerified(queryFactory)
}

@Test
fun updateQuery() {
// given
every { queryFactory.updateQuery<Data1>(any(), any()) } returns typedQuery

val dsl: CriteriaUpdateQueryDsl.() -> Unit = {
set(col(Data1::id), 1)
where(col(Data1::id).equal(2))
}

// when
val actual: Query = queryFactory.updateQuery<Data1>(dsl)

// then
assertThat(actual).isEqualTo(typedQuery)

verify(exactly = 1) {
queryFactory.updateQuery(Data1::class, dsl)
}

confirmVerified(queryFactory)
}

@Test
fun deleteQuery() {
// given
every { queryFactory.deleteQuery<Data1>(any(), any()) } returns typedQuery

val dsl: CriteriaDeleteQueryDsl.() -> Unit = {
where(col(Data1::id).equal(1))
}

// when
val actual: Query = queryFactory.deleteQuery<Data1>(dsl)

// then
assertThat(actual).isEqualTo(typedQuery)

verify(exactly = 1) {
queryFactory.deleteQuery(Data1::class, dsl)
}

confirmVerified(queryFactory)
}


@Test
fun subquery() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,38 @@ internal class QueryFactoryImplTest : WithKotlinJdslAssertions {
confirmVerified(criteriaQueryCreator)
}

@Test
fun deleteQuery() {
// given
val query: Query = mockk()

every { criteriaQueryCreator.createQuery(any<QueryDslImpl.CriteriaDeleteQuerySpecImpl<Data1>>()) } returns query

// when
val actual = sut.deleteQuery(Data1::class) {
where(col(Data1::id).equal(1))
}

// then
assertThat(actual).isEqualTo(query)

verify(exactly = 1) {
val columnSpec = ColumnSpec<Int>(EntitySpec(Data1::class.java), Data1::id.name)
criteriaQueryCreator.createQuery(
QueryDslImpl.CriteriaDeleteQuerySpecImpl(
from = FromClause(EntitySpec(Data1::class.java)),
associate = SimpleAssociatedJoinClause(emptyList()),
where = WhereClause(EqualValueSpec(columnSpec, 1)),
jpaHint = JpaQueryHintClauseImpl(emptyMap()),
sqlHint = EmptySqlQueryHintClause,
targetEntity = Data1::class.java
)
)
}

confirmVerified(criteriaQueryCreator)
}

@Test
fun subquery() {
// when
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.linecorp.kotlinjdsl.query.creator

import com.linecorp.kotlinjdsl.query.CriteriaDeleteQuerySpec
import com.linecorp.kotlinjdsl.query.CriteriaQuerySpec
import com.linecorp.kotlinjdsl.query.CriteriaUpdateQuerySpec
import com.linecorp.kotlinjdsl.query.clause.from.FromClause
Expand All @@ -25,11 +26,9 @@ import org.junit.jupiter.api.extension.ExtendWith
import javax.persistence.EntityManager
import javax.persistence.Query
import javax.persistence.TypedQuery
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.CriteriaQuery
import javax.persistence.criteria.CriteriaUpdate
import javax.persistence.criteria.Path
import javax.persistence.criteria.*

@Suppress("UnusedEquals")
@ExtendWith(MockKExtension::class)
internal class CriteriaQueryCreatorImplTest : WithKotlinJdslAssertions {
@InjectMockKs
Expand Down Expand Up @@ -200,4 +199,67 @@ internal class CriteriaQueryCreatorImplTest : WithKotlinJdslAssertions {
em, froms, criteriaBuilder
)
}

@Suppress("UNCHECKED_CAST")
@Test
fun createDeleteQuery() {
data class TestCriteriaDeleteQuerySpec<T>(
override val targetEntity: Class<T>,
override val from: FromClause,
override val associate: SimpleAssociatedJoinClause,
override val where: CriteriaQueryWhereClause,
override val jpaHint: JpaQueryHintClause,
override val sqlHint: SqlQueryHintClause,
) : CriteriaDeleteQuerySpec<T>
// given
val createdQuery: CriteriaDelete<Int> = mockk()
val query: Query = mockk()

val from: FromClause = mockk()
val associate = SimpleAssociatedJoinClause(emptyList())
val where: CriteriaQueryWhereClause = mockk()
val jpaHint: JpaQueryHintClause = mockk()
val sqlHint: SqlQueryHintClause = mockk()
val set: SetClause = mockk()

val spec: CriteriaDeleteQuerySpec<Int> = TestCriteriaDeleteQuerySpec(
from = from,
associate = associate,
where = where,
jpaHint = jpaHint,
sqlHint = sqlHint,
targetEntity = Int::class.java
)

every { em.criteriaBuilder } returns criteriaBuilder
every { em.createQuery(createdQuery) } returns query
every { criteriaBuilder.createCriteriaDelete(Int::class.java) } returns createdQuery
every { from.associate(associate, createdQuery as CriteriaDelete<in Any>, Int::class.java) } returns froms
every { where.apply(froms, createdQuery, criteriaBuilder) } just runs
every { jpaHint.apply(query) } just runs
every { sqlHint.apply(query) } just runs

// when
val actual = sut.createQuery(spec)

// then
assertThat(actual).isEqualTo(query)

verify(exactly = 1) {
em.criteriaBuilder
em.createQuery(createdQuery)
criteriaBuilder.createCriteriaDelete(Int::class.java)
from.associate(associate, createdQuery as CriteriaDelete<in Any>, Int::class.java)
where.apply(froms, createdQuery, criteriaBuilder)
jpaHint.apply(query)
sqlHint.apply(query)
query == query
}

confirmVerified(
from, where, jpaHint, sqlHint, set,
createdQuery, query,
em, froms, criteriaBuilder
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.linecorp.kotlinjdsl.eclipselink.integration.criteriaquery

import com.linecorp.kotlinjdsl.test.integration.EntityManagerExtension
import com.linecorp.kotlinjdsl.test.integration.criteriaquery.AbstractCriteriaDeleteIntegrationTest
import org.junit.jupiter.api.extension.ExtendWith
import javax.persistence.EntityManager

@ExtendWith(EntityManagerExtension::class)
class EclipselinkCriteriaDeleteIntegrationTest : AbstractCriteriaDeleteIntegrationTest() {
override lateinit var entityManager: EntityManager
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.linecorp.kotlinjdsl.hibernate.criteriaquery

import com.linecorp.kotlinjdsl.test.integration.EntityManagerExtension
import com.linecorp.kotlinjdsl.test.integration.criteriaquery.AbstractCriteriaDeleteIntegrationTest
import org.junit.jupiter.api.extension.ExtendWith
import javax.persistence.EntityManager

@ExtendWith(EntityManagerExtension::class)
class HibernateCriteriaDeleteIntegrationTest : AbstractCriteriaDeleteIntegrationTest() {
override lateinit var entityManager: EntityManager
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.linecorp.kotlinjdsl.query

import com.linecorp.kotlinjdsl.query.clause.from.FromClause
import com.linecorp.kotlinjdsl.query.clause.from.SimpleAssociatedJoinClause
import com.linecorp.kotlinjdsl.query.clause.hint.JpaQueryHintClause
import com.linecorp.kotlinjdsl.query.clause.hint.SqlQueryHintClause
import com.linecorp.kotlinjdsl.query.clause.where.CriteriaQueryWhereClause

interface CriteriaDeleteQuerySpec<T> {
val targetEntity: Class<T>
val from: FromClause
val associate: SimpleAssociatedJoinClause
val where: CriteriaQueryWhereClause
val jpaHint: JpaQueryHintClause
val sqlHint: SqlQueryHintClause
}
Loading

0 comments on commit 2612fec

Please sign in to comment.