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

Introduce QueryDsl to unify Entityql and NativeSql APIs #1203

Merged
merged 8 commits into from
Oct 14, 2024

Conversation

nakamura-to
Copy link
Member

@nakamura-to nakamura-to commented Oct 12, 2024

In the previous Criteria API, it was necessary to use two different types of DSL as needed:

  • Entityql: A DSL for handling entities, taking into account the identity of each entity.
  • NativeSql: A DSL for executing SQL independently of entities. When working with entities, their identity is not considered, and they are treated as simple DTOs.

This pull request introduces a new DSL called QueryDsl. QueryDsl provides users with an intuitive API and determines the need to consider entity identity based on the invoked API.

By using QueryDsl, users no longer have to worry about switching between Entityql and NativeSql.


Here is a sample code comparing cases with and without using QueryDsl.

with QueryDsl

// Define only one DSL.
var dsl = new QueryDsl(config);

var e = new Employee_();
var d = new Department_();

var employees = dsl.from(e)
    .leftJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .associate(e, d, Employee::setDepartment)
    .fetch();

for (var employee : employees) {
    if (employee.getDepartment() == null) {
        dsl.delete(e).single(employee).execute();
    } else {
        // We can use QueryDsl to update a specific column
        dsl.update(e).set(c -> c.value(e.salary, employee.getSalary().add(1000))).execute();
    }
}

without QueryDsl

// Define two DSLs.
var entityql = new Entityql(config);
var nativeSql = new NativeSql(config);

var e = new Employee_();
var d = new Department_();

var employees = entityql.from(e)
    .leftJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .associate(e, d, Employee::setDepartment)
    .fetch();

for (var employee : employees) {
    if (employee.getDepartment() == null) {
        entityql.delete(e, employee).execute();
    } else {
        // We must use NaitveSql to update a specific column
        nativeSql.update(e).set(c -> c.value(e.salary, employee.getSalary().add(1000))).execute();
    }
}

import org.seasar.doma.kotlin.jdbc.criteria.declaration.KValuesDeclaration
import java.util.*

class KUnifiedInsertStating<ENTITY : Any>(private val statement: UnifiedInsertStarting<ENTITY>) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I found typo.

x: KUnifiedInsertStating
o: KUnifiedInsertStarting

The following class is the same.

org.seasar.doma.kotlin.jdbc.criteria.statement.KUnifiedDeleteStating
org.seasar.doma.kotlin.jdbc.criteria.statement.KUnifiedInsertStating
org.seasar.doma.kotlin.jdbc.criteria.statement.KUnifiedSelectStating
org.seasar.doma.kotlin.jdbc.criteria.statement.KUnifiedUpdateStating

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch! Thanks!

Comment on lines +73 to +81
/**
* Deletes all the entities.
*
* @return the delete statement
*/
public Statement<Integer> all() {
settings.setAllowEmptyWhere(true);
return asNativeSqlDeleteStarting();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It good to consistency with the single and batch methods. 👍
Previously, Require set to DeleteCofig { allowEmptyWhere = true }.

return asEntityqlSelectStarting().createCommand();
}

private UnifiedSelectTerminal<ENTITY> asUnifiedSelectTerminal() {
Copy link
Contributor

@momosetkn momosetkn Oct 12, 2024

Choose a reason for hiding this comment

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

I want to use this method by client-code.
Because I want to prevent the class of the receiver for the associate method changes between the first and subsequent calls.

val query = queryDsl.from(a)
    .leftJoin(b) {
        eq(a.id, b.aId)
    }
val associatedQuery = query
    .associate( /* omission */ ) // receiver class is UnifiedSelectStarting
    .associate( /* omission */ ) // receiver class is UnifiedSelectTerminal
    .associate( /* omission */ ) // receiver class is UnifiedSelectTerminal

I accept automatically change to UnifiedSelectTerminal, when call UnifiedSelectStarting#associate method. but, I want manually change receiver.

When decomposed query into a separate method, it would require double implementation through overloading like bellow code.

fun findList1() {
    val query = queryDsl.from(a)
        .leftJoin(b) {
            eq(a.id, b.aId)
        }
        .associateTableB() // use first associateTableB method
}

fun findList2() {
    val query = queryDsl.from(a)
        .leftJoin(b) {
            eq(a.id, b.aId)
        }
        .associateTableA()
        .associateTableB() // use second associateTableB method
}

fun UnifiedSelectStarting<TableA>.associateTableA(): UnifiedSelectTerminal<TableA> {
    return this.associate( /* omission */ )
}
// overload
fun UnifiedSelectTerminal<TableA>.associateTableA(): UnifiedSelectTerminal<TableA> {
    return this.associate( /* omission */ )
}

fun UnifiedSelectStarting<TableB>.associateTableB(): UnifiedSelectTerminal<TableB> {
    return this.associate( /* omission */ )
}
// overload
fun UnifiedSelectTerminal<TableB>.associateTableB(): UnifiedSelectTerminal<TableB> {
    return this.associate( /* omission */ )
}

Copy link
Member Author

Choose a reason for hiding this comment

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

In commit 40107f4, we introduced the EntityQueryable and KEntityQueryable interfaces to abstract the common elements of UnifiedSelectStarting and UnifiedSelectTerminal. Do these changes help resolve the issues mentioned above?

Copy link
Contributor

Choose a reason for hiding this comment

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

This abstract receiver class for consistent logic. that is helpful.
thank you.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good that the separation of queries and concerns. 👍
Previously, put code everything EntityqlSelectStarting.

@nakamura-to nakamura-to merged commit ac6ffc5 into master Oct 14, 2024
11 checks passed
@nakamura-to nakamura-to deleted the feat/unified-criteria-api branch October 14, 2024 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants