Skip to content

Commit

Permalink
Merge pull request #52 from cj848/feature/support-treat
Browse files Browse the repository at this point in the history
Support Treat
  • Loading branch information
Hyunsik Kang authored Apr 28, 2022
2 parents c5c0f91 + d260cfe commit 1e36a6d
Show file tree
Hide file tree
Showing 34 changed files with 1,515 additions and 55 deletions.
159 changes: 159 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,165 @@ val deletedRowCount = queryFactory.deleteQuery<OrderAddress> {
}.executeUpdate()
```

#### Treat (Downcasting)

There may be situations where you need to downcast when using an Entity of an inheritance structure. In that case, you can use the code like below.


```kotlin
val employees = queryFactory.listQuery<Employee> {
selectDistinct(entity(Employee::class))
from(entity(Employee::class))
treat<Employee, PartTimeEmployee>()
where(
col(PartTimeEmployee::weeklySalary).lessThan(1000.toBigDecimal()),
)
}
```

If you are downcasting from the root entity (Project Entity in this case) that contains an entity with inheritance structure, you can do it as follows.

```kotlin
val projects = queryFactory.listQuery<Project> {
selectDistinct(entity(Project::class))
from(entity(Project::class))

treat<Employee, FullTimeEmployee>(col(Project::employees))
where(
col(FullTimeEmployee::annualSalary).greaterThan(100000.toBigDecimal())
)
}
```

For Hibernate, the issue at [issue](https://discourse.hibernate.org/t/jpa-downcast-criteria-treat-vs-jpql-treat/2231) is currently unresolved and an additional inner(left) join is added to make the result It may come out as a duplicate.
So you should always apply distinct to select above like examples

If you are using Hibernate and want to fetch downcasting entities, the query cannot be executed normally. That is, the example below will result in a runtime error because of this [issue](https://discourse.hibernate.org/t/can-fetch-be-used-as-parameter-of-treat-for-downcasting/3301).

```kotlin
val sub = queryFactory.subquery<Long> {
select(col(Project::id))
from(entity(Project::class))

treat<Employee, FullTimeEmployee>(col(Project::employees))
treat<Employee, PartTimeEmployee>(col(Project::employees))
where(
or(
col(FullTimeEmployee::annualSalary).greaterThan(100000.toBigDecimal()),
col(PartTimeEmployee::weeklySalary).greaterThan(1000.toBigDecimal()),
)
)
}
val projects = queryFactory.listQuery<Project> {
val project = Project::class.alias("dedupeProject")
selectDistinct(project)
from(project)
val supervisor = Employee::class.alias("super")
val partTimeSuper = PartTimeEmployee::class.alias("partSuper")
// If you are using Hibernate and want to fetch downcasting entities, the query cannot be executed normally. That is, the example below will result in a runtime error.
fetch(project, supervisor, on(Project::supervisor))
treat(ColumnSpec<PartTimeEmployee>(project, Project::supervisor.name), supervisor, partTimeSuper)
where(
and(
col(project, Project::id).`in`(sub),
col(partTimeSuper, PartTimeEmployee::weeklySalary).equal(900.toBigDecimal()),
)
)
}
```

If you want to use downcasting entity in select clause, Eclipselink does not support that function. An example is as follows.

```kotlin
val employees = queryFactory.listQuery<FullTimeEmployee> {
val project: EntitySpec<Project> = Project::class.alias("project")
val fullTimeEmployee = FullTimeEmployee::class.alias("fe")
val employee = Employee::class.alias("e")

selectDistinct(fullTimeEmployee)
from(project)
treat(ColumnSpec<FullTimeEmployee>(project, Project::employees.name), employee, fullTimeEmployee)
where(
ColumnSpec<BigDecimal>(fullTimeEmployee, FullTimeEmployee::annualSalary.name)
.greaterThan(100000.toBigDecimal())
)
}

```

The Entity structure corresponds to the following structure.

```kotlin
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Entity
@Table(name = "employee")
@DiscriminatorColumn(name = "EMP_TYPE")
class Employee(
@Id
@GeneratedValue
val id: Long,
val name: String
) {
override fun equals(other: Any?) = Objects.equals(id, (other as? Employee)?.id)
override fun hashCode() = Objects.hashCode(id)
}

@Entity
@Table(name = "fulltime_employee")
@DiscriminatorValue("F")
class FullTimeEmployee(
val annualSalary: BigDecimal,
override val id: Long,
override val name: String
) : Employee(id, name) {
override fun equals(other: Any?) = Objects.equals(id, (other as? FullTimeEmployee)?.id)
override fun hashCode() = Objects.hashCode(id)
}

@Entity
@Table(name = "parttime_employee")
@DiscriminatorValue("P")
class PartTimeEmployee(
val weeklySalary: BigDecimal,
override val id: Long,
override val name: String
) : Employee(id, name) {
override fun equals(other: Any?) = Objects.equals(id, (other as? PartTimeEmployee)?.id)
override fun hashCode() = Objects.hashCode(id)
}

@Entity
@Table(name = "contract_employee")
@DiscriminatorValue("C")
class ContractEmployee(
val hourlyRate: BigDecimal,
override val id: Long,
override val name: String
) : Employee(id, name) {
override fun equals(other: Any?) = Objects.equals(id, (other as? ContractEmployee)?.id)
override fun hashCode() = Objects.hashCode(id)
}

@Entity
@Table(name = "project")
class Project(
@Id
@GeneratedValue
val id: Long = 0,
val name: String,

@OneToMany(cascade = [CascadeType.ALL])
val employees: List<Employee>,

@OneToOne(cascade = [CascadeType.ALL], optional = false, fetch = FetchType.LAZY)
val supervisor: Employee
) {
override fun equals(other: Any?) = Objects.equals(id, (other as? Project)?.id)
override fun hashCode() = Objects.hashCode(id)
}

```


## How it works

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ plugins {

allprojects {
group = "com.linecorp.kotlin-jdsl"
version = "2.0.1.RELEASE"
version = "2.0.2.RELEASE"

repositories {
mavenCentral()
Expand Down
20 changes: 10 additions & 10 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import org.gradle.api.artifacts.dsl.DependencyHandler

object Dependencies {
const val kotlinVersion = "1.6.10"
const val springCoreVersion = "5.3.16"
const val springBootVersion = "2.6.4"
const val springDataJpaVersion = "2.6.2"
const val coroutineVersion = "1.6.0"
const val kotlinVersion = "1.6.21"
const val springCoreVersion = "5.3.19"
const val springBootVersion = "2.6.7"
const val springDataJpaVersion = "2.6.4"
const val coroutineVersion = "1.6.1"

// kotlin
const val koltin = "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
Expand All @@ -16,13 +16,13 @@ object Dependencies {
// Common
const val javaPersistenceApi = "javax.persistence:javax.persistence-api:2.2"
const val slf4j = "org.slf4j:slf4j-api:1.7.36"
const val logback = "ch.qos.logback:logback-classic:1.2.10"
const val hibernate = "org.hibernate:hibernate-core:5.6.5.Final"
const val hibernateReactive = "org.hibernate.reactive:hibernate-reactive-core:1.1.3.Final"
const val logback = "ch.qos.logback:logback-classic:1.2.11"
const val hibernate = "org.hibernate:hibernate-core:5.6.8.Final"
const val hibernateReactive = "org.hibernate.reactive:hibernate-reactive-core:1.1.4.Final"
const val eclipselink = "org.eclipse.persistence:org.eclipse.persistence.jpa:2.7.10"
const val jacksonKotlinModule = "com.fasterxml.jackson.module:jackson-module-kotlin"
const val agroalPool = "io.agroal:agroal-pool:1.14"
const val vertxJdbcClient = "io.vertx:vertx-jdbc-client:4.2.5"
const val vertxJdbcClient = "io.vertx:vertx-jdbc-client:4.2.7"
const val coroutineJdk8 = "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutineVersion"
const val coroutineReactor = "org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutineVersion"

Expand All @@ -47,7 +47,7 @@ object Dependencies {
// Test
const val junit = "org.junit.jupiter:junit-jupiter:5.8.2"
const val assertJ = "org.assertj:assertj-core:3.22.0"
const val mockk = "io.mockk:mockk:1.12.2"
const val mockk = "io.mockk:mockk:1.12.3"

const val h2 = "com.h2database:h2:1.4.200"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,15 @@ open class QueryDslImpl<T>(
lazyJoins().add(SimpleAssociatedJoinSpec(left = left, right = right, path = relation.path))
}

override fun <P, C : P> treat(
root: ColumnSpec<*>,
parent: EntitySpec<P>,
child: EntitySpec<C>,
parentJoinType: JoinType
) {
lazyJoins().add(TreatJoinSpec(parent, child, parentJoinType, root))
}

override fun <T, R> fetch(
left: EntitySpec<T>,
right: EntitySpec<R>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ internal class CriteriaQueryCreatorImplTest : WithKotlinJdslAssertions {
every { em.criteriaBuilder } returns criteriaBuilder
every { em.createQuery(createdQuery) } returns typedQuery
every { criteriaBuilder.createQuery(Int::class.java) } returns createdQuery
every { from.join(join, createdQuery) } returns froms
every { from.join(join, createdQuery, criteriaBuilder) } returns froms
every { select.apply(froms, createdQuery, criteriaBuilder) } just runs
every { where.apply(froms, createdQuery, criteriaBuilder) } just runs
every { groupBy.apply(froms, createdQuery, criteriaBuilder) } just runs
Expand All @@ -110,7 +110,7 @@ internal class CriteriaQueryCreatorImplTest : WithKotlinJdslAssertions {
em.criteriaBuilder
em.createQuery(createdQuery)
criteriaBuilder.createQuery(Int::class.java)
from.join(join, createdQuery)
from.join(join, createdQuery, criteriaBuilder)
select.returnType
select.apply(froms, createdQuery, criteriaBuilder)
where.apply(froms, createdQuery, criteriaBuilder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import com.linecorp.kotlinjdsl.query.clause.where.WhereClause
import com.linecorp.kotlinjdsl.query.spec.CrossJoinSpec
import com.linecorp.kotlinjdsl.query.spec.SimpleAssociatedJoinSpec
import com.linecorp.kotlinjdsl.query.spec.SimpleJoinSpec
import com.linecorp.kotlinjdsl.query.spec.TreatJoinSpec
import com.linecorp.kotlinjdsl.query.spec.expression.ColumnSpec
import com.linecorp.kotlinjdsl.query.spec.expression.EntitySpec
import com.linecorp.kotlinjdsl.query.spec.predicate.AndSpec
import com.linecorp.kotlinjdsl.query.spec.predicate.PredicateSpec
import com.linecorp.kotlinjdsl.querydsl.QueryDslImpl
import com.linecorp.kotlinjdsl.test.WithKotlinJdslAssertions
import com.linecorp.kotlinjdsl.test.entity.employee.Employee
import com.linecorp.kotlinjdsl.test.entity.employee.FullTimeEmployee
import com.linecorp.kotlinjdsl.test.entity.employee.Project
import io.mockk.mockk
import org.junit.jupiter.api.Test
import java.math.BigDecimal
import javax.persistence.criteria.JoinType

internal class QueryDslImplJoinTest : WithKotlinJdslAssertions {
Expand Down Expand Up @@ -107,6 +113,49 @@ internal class QueryDslImplJoinTest : WithKotlinJdslAssertions {
)
}

@Test
fun treat() {
// when
val actual = QueryDslImpl(FullTimeEmployee::class.java).apply {
val project: EntitySpec<Project> = Project::class.alias("project")
val fullTimeEmployee = FullTimeEmployee::class.alias("fe")
val employee = Employee::class.alias("e")

selectDistinct(fullTimeEmployee)
from(project)
treat(
root = ColumnSpec<FullTimeEmployee>(entity = project, path = Project::employees.name),
parent = employee,
child = fullTimeEmployee,
parentJoinType = JoinType.RIGHT
)
where(
ColumnSpec<BigDecimal>(fullTimeEmployee, FullTimeEmployee::annualSalary.name)
.greaterThan(100000.toBigDecimal())
)
}

// then
val joinSpec = TreatJoinSpec(
left = EntitySpec(Employee::class.java, "e"),
right = EntitySpec(FullTimeEmployee::class.java, "fe"),
joinType = JoinType.RIGHT,
root = ColumnSpec<FullTimeEmployee>(EntitySpec(Project::class.java, "project"), Project::employees.name),
)

val criteriaQuerySpec = actual.createCriteriaQuerySpec()

assertThat(criteriaQuerySpec.join).isEqualTo(
JoinClause(listOf(joinSpec))
)

val subquerySpec = actual.createSubquerySpec()

assertThat(subquerySpec.join).isEqualTo(
JoinClause(listOf(joinSpec))
)
}

@Test
fun updateAssociate() {
// when
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.linecorp.kotlinjdsl.eclipselink.integration.querydsl.from

import com.linecorp.kotlinjdsl.test.integration.querydsl.from.AbstractCriteriaQueryDslFromTreatIntegrationTest
import org.eclipse.persistence.exceptions.QueryException
import org.junit.jupiter.api.Test

internal class EclipselinkCriteriaQueryDslFromTreatIntegrationTest :
AbstractCriteriaQueryDslFromTreatIntegrationTest() {
/**
* except Hibernate, select downcasting entity is prohibited
*/
@Test
override fun getProjectByFullTimeEmployeesSalarySelectFullTimeEmployee() {
assertThatThrownBy { super.getProjectByFullTimeEmployeesSalarySelectFullTimeEmployee() }
.hasRootCauseExactlyInstanceOf(QueryException::class.java)
}
}
9 changes: 8 additions & 1 deletion eclipselink/src/test/resources/META-INF/persistence.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">

<persistence-unit name="order">
<class>com.linecorp.kotlinjdsl.test.entity.employee.ContractEmployee</class>
<class>com.linecorp.kotlinjdsl.test.entity.employee.Employee</class>
<class>com.linecorp.kotlinjdsl.test.entity.employee.FullTimeEmployee</class>
<class>com.linecorp.kotlinjdsl.test.entity.employee.PartTimeEmployee</class>
<class>com.linecorp.kotlinjdsl.test.entity.employee.Project</class>

<class>com.linecorp.kotlinjdsl.test.entity.Address</class>

<class>com.linecorp.kotlinjdsl.test.entity.order.Order</class>
Expand All @@ -18,7 +24,8 @@
<class>com.linecorp.kotlinjdsl.test.entity.test.TestTable</class>

<properties>
<property name="eclipselink.logging.level" value="FINE" />
<property name="eclipselink.logging.level" value="INFO" />
<property name="eclipselink.logging.level.sql" value="FINE" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:test;MODE=MYSQL"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ internal class MutinyReactiveCriteriaQueryCreatorTest : WithKotlinJdslAssertions

every { session.createQuery(createdQuery) } returns stageQuery
every { criteriaBuilder.createQuery(Int::class.java) } returns createdQuery
every { from.join(join, createdQuery) } returns froms
every { from.join(join, createdQuery, criteriaBuilder) } returns froms
every { select.apply(froms, createdQuery, criteriaBuilder) } just runs
every { where.apply(froms, createdQuery, criteriaBuilder) } just runs
every { groupBy.apply(froms, createdQuery, criteriaBuilder) } just runs
Expand All @@ -104,7 +104,7 @@ internal class MutinyReactiveCriteriaQueryCreatorTest : WithKotlinJdslAssertions
verify(exactly = 1) {
session.createQuery(createdQuery)
criteriaBuilder.createQuery(Int::class.java)
from.join(join, createdQuery)
from.join(join, createdQuery, criteriaBuilder)
select.returnType
select.apply(froms, createdQuery, criteriaBuilder)
where.apply(froms, createdQuery, criteriaBuilder)
Expand Down
Loading

0 comments on commit 1e36a6d

Please sign in to comment.