Unified Criteria API

Introduction

The Unified Criteria API unifies the Classic Criteria API, Entityql and NativeSql DSLs, providing a clear and intuitive interface.

The following entity classes are used in the examples below:

@Entity(metamodel = @Metamodel)
public class Employee {

  @Id private Integer employeeId;
  private Integer employeeNo;
  private String employeeName;
  private Integer managerId;
  private LocalDate hiredate;
  private Salary salary;
  private Integer departmentId;
  private Integer addressId;
  @Version private Integer version;
  @OriginalStates private Employee states;
  @Transient private Department department;
  @Transient private Employee manager;
  @Transient private Address address;

  // getters and setters
}
@Entity(metamodel = @Metamodel)
public class Department {

  @Id private Integer departmentId;
  private Integer departmentNo;
  private String departmentName;
  private String location;
  @Version private Integer version;
  @OriginalStates private Department originalStates;
  @Transient private List<Employee> employeeList = new ArrayList<>();

  // getters and setters
}
@Entity(immutable = true, metamodel = @Metamodel)
@Table(name = "EMPLOYEE")
public class Emp {

  @Id private final Integer employeeId;
  private final Integer employeeNo;
  private final String employeeName;
  private final Integer managerId;
  private final LocalDate hiredate;
  private final Salary salary;
  private final Integer departmentId;
  private final Integer addressId;
  @Version private final Integer version;
  @Transient private final Dept department;
  @Transient private final Emp manager;

  // constructor and getters
}
@Entity(immutable = true, metamodel = @Metamodel)
@Table(name = "DEPARTMENT")
public class Dept {

  @Id private final Integer departmentId;
  private final Integer departmentNo;
  private final String departmentName;
  private final String location;
  @Version private final Integer version;

  // constructor and getters
}

Note that the above classes are annotated with @Entity(metamodel = @Metamodel). The metamodel = @Metamodel indicates that the annotated entity has a corresponding metamodel class generated by Doma’s annotation processor.

In our examples, the metamodel classes are Employee_, Department_, Emp_ and Dept_. These metamodels enable type-safe query creation.

You can customize the metamodel names using the elements in the Metamodel annotation.

To bulk customize all metamodels, you can use annotation processor options. See Annotation processing and refer to the following options:

  • doma.metamodel.enabled

  • doma.metamodel.prefix

  • doma.metamodel.suffix

Query DSL

The Unified Criteria API is, in essence, the Query DSL.

The Query DSL can perform entity queries and associations. The entry point is the org.seasar.doma.jdbc.criteria.QueryDsl class. This class includes the following methods:

  • from

  • insert

  • delete

  • update

Instantiate the QueryDsl class as follows:

QueryDsl queryDsl = new QueryDsl(config);

For example, to query Employee and Department entities and associate them, use:

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

List<Employee> list =
    queryDsl
        .from(e)
        .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
        .where(c -> c.eq(d.departmentName, "SALES"))
        .associate(
            e,
            d,
            (employee, department) -> {
              employee.setDepartment(department);
              department.getEmployeeList().add(employee);
            })
        .fetch();

The query above generates the following SQL statement:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID,
t0_.HIREDATE, t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION,
t1_.DEPARTMENT_ID, t1_.DEPARTMENT_NO, t1_.DEPARTMENT_NAME, t1_.LOCATION, t1_.VERSION
from EMPLOYEE t0_ inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)
where t1_.DEPARTMENT_NAME = ?

Note

In Kotlin, use org.seasar.doma.kotlin.jdbc.criteria.KQueryDsl instead of QueryDsl. KQueryDsl is included in the doma-kotlin module. See Kotlin specific Criteria API.

Select Statement

Select Settings

We support the following settings:

  • allowEmptyWhere

  • comment

  • fetchSize

  • maxRows

  • queryTimeout

  • sqlLogType

All are optional and can be applied as follows:

Employee_ e = new Employee_();

List<Employee> list = queryDsl.from(e, settings -> {
  settings.setAllowEmptyWhere(false);
  settings.setComment("all employees");
  settings.setFetchSize(100);
  settings.setMaxRows(100);
  settings.setSqlLogType(SqlLogType.RAW);
  settings.setQueryTimeout(1000);
}).fetch();

Fetching

The Query DSL provides the following data-fetching methods:

  • fetch

  • fetchOne

  • fetchOptional

  • stream

Employee_ e = new Employee_();

// The fetch method returns results as a list.
List<Employee> list = queryDsl.from(e).fetch();

// The fetchOne method returns a single result, possibly null.
Employee employee = queryDsl.from(e).where(c -> c.eq(e.employeeId, 1)).fetchOne();

// The fetchOptional method returns a single result as an Optional object.
Optional<Employee> optional = queryDsl.from(e).where(c -> c.eq(e.employeeId, 1)).fetchOptional();

// The stream method returns results as a stream.
Stream<Employee> stream = queryDsl.from(e).stream();

Streaming

The Query DSL supports the following stream-handling methods:

  • mapStream

  • collect

  • openStream

Employee_ e = new Employee_();

// mapStream allows processing of a stream.
Map<Integer, List<Employee>> map = queryDsl
    .from(e)
    .mapStream(stream -> stream.collect(groupingBy(Employee::getDepartmentId)));

// collect is a shorthand for mapStream.
Map<Integer, List<Employee>> map2 = queryDsl.from(e).collect(groupingBy(Employee::getDepartmentId));

// openStream returns a stream. You MUST close the stream explicitly.
try (Stream<Employee> stream = queryDsl.from(e).openStream()) {
    stream.forEach(employee -> {
        // do something
    });
}

These methods provide efficient processing for large result sets.

Select Expression

Entity Selection

By default, the result entity type is the same as the type specified in the from method:

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

List<Employee> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .fetch();

The above query generates the following SQL statement:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID,
t0_.HIREDATE, t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)

To choose a joined entity type as the result entity type, use project or select:

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

List<Department> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .project(d)
    .fetch();

This query generates the following SQL:

select t1_.DEPARTMENT_ID, t1_.DEPARTMENT_NO, t1_.DEPARTMENT_NAME, t1_.LOCATION, t1_.VERSION
from EMPLOYEE t0_
inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)

Note

The project method removes duplicate entities, while select does not. If you call neither method, duplicates are removed by default.

Multiple Entity Selection

Specify multiple entity types and fetch them as tuples:

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

List<Tuple2<Department, Employee>> list = queryDsl
    .from(d)
    .leftJoin(e, on -> on.eq(d.departmentId, e.departmentId))
    .where(c -> c.eq(d.departmentId, 4))
    .select(d, e)
    .fetch();

This query generates:

select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION,
t0_.VERSION, t1_.EMPLOYEE_ID, t1_.EMPLOYEE_NO, t1_.EMPLOYEE_NAME, t1_.MANAGER_ID,
t1_.HIREDATE, t1_.SALARY, t1_.DEPARTMENT_ID, t1_.ADDRESS_ID, t1_.VERSION
from DEPARTMENT t0_ left outer join EMPLOYEE t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)
where t0_.DEPARTMENT_ID = ?

In the tuple, an entity is null if all its properties are null.

Note

The select method does not remove duplicates.

Column Projection

To project columns, use select. For one column:

Employee_ e = new Employee_();

List<String> list = queryDsl.from(e).select(e.employeeName).fetch();

This generates:

select t0_.EMPLOYEE_NAME from EMPLOYEE t0_

For multiple columns:

Employee_ e = new Employee_();

List<Tuple2<String, Integer>> list = queryDsl
    .from(e)
    .select(e.employeeName, e.employeeNo)
    .fetch();

This generates:

select t0_.EMPLOYEE_NAME, t0_.EMPLOYEE_NO from EMPLOYEE t0_

Columns up to 9 are held in Tuple2 to Tuple9. Beyond that, they are held in Row.

Use selectAsRow for a Row list:

Employee_ e = new Employee_();

List<Row> list = queryDsl.from(e).selectAsRow(e.employeeName, e.employeeNo).fetch();

Column Projection and Mapping

To project columns and map them to an entity, use the projectTo or selectTo methods:

Employee_ e = new Employee_();

List<Employee> list = queryDsl.from(e).selectTo(e, e.employeeName).fetch();

This query generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME from EMPLOYEE t0_

Note that the SQL select clause includes the primary key “EMPLOYEE_ID”. The projectTo and selectTo methods always include the entity’s ID properties, even if they aren’t explicitly specified.

Note

The projectTo method removes duplicate entity IDs from the results, while selectTo does not.

Where Expression

The following operators and predicates are supported:

  • eq - (=)

  • ne - (<>)

  • ge - (>=)

  • gt - (>)

  • le - (<=)

  • lt - (<)

  • isNull - (is null)

  • isNotNull - (is not null)

  • like

  • notLike - (not like)

  • between

  • in

  • notIn - (not in)

  • exists

  • notExists - (not exists)

Note

If the right-hand operand is null, the WHERE or HAVING clause will exclude the operator. See WhereDeclaration and HavingDeclaration javadoc for details.

We also support utility operators:

  • eqOrIsNull - (“=” or “is null”)

  • neOrIsNotNull - (“<>” or “is not null”)

Additionally, the following logical operators are supported:

  • and

  • or

  • not

Employee_ e = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .where(c -> {
        c.eq(e.departmentId, 2);
        c.isNotNull(e.managerId);
        c.or(() -> {
            c.gt(e.salary, new Salary("1000"));
            c.lt(e.salary, new Salary("2000"));
        });
    })
    .fetch();

This generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
where t0_.DEPARTMENT_ID = ? and t0_.MANAGER_ID is not null or (t0_.SALARY > ? and t0_.SALARY < ?)

Subqueries can be written as follows:

Employee_ e = new Employee_();
Employee_ e2 = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .where(c -> c.in(e.employeeId, c.from(e2).select(e2.managerId)))
    .orderBy(c -> c.asc(e.employeeId))
    .fetch();

The above query generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
where t0_.EMPLOYEE_ID in (select t1_.MANAGER_ID from EMPLOYEE t1_)
order by t0_.EMPLOYEE_ID asc

Dynamic Where Expression

A WHERE expression uses only evaluated operators to build a WHERE clause. When no operators are evaluated in the expression, the statement omits the WHERE clause.

For example, with a conditional expression:

Employee_ e = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .where(c -> {
        c.eq(e.departmentId, 1);
        if (enableNameCondition) {
            c.like(e.employeeName, name);
        }
    })
    .fetch();

If enableNameCondition is false, the like expression is ignored, generating:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_ where t0_.DEPARTMENT_ID = ?

Join Expression

We support the following join expressions:

  • innerJoin - (inner join)

  • leftJoin - (left outer join)

Example for innerJoin:

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

List<Employee> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .fetch();

This generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)

Example for leftJoin:

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

List<Employee> list = queryDsl
    .from(e)
    .leftJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .fetch();

This generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
left outer join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)

Association

You can associate entities using the associate operation in conjunction with a join expression:

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

List<Employee> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .where(c -> c.eq(d.departmentName, "SALES"))
    .associate(
        e,
        d,
        (employee, department) -> {
          employee.setDepartment(department);
          department.getEmployeeList().add(employee);
        })
    .fetch();

This query generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID,
t0_.HIREDATE, t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION,
t1_.DEPARTMENT_ID, t1_.DEPARTMENT_NO, t1_.DEPARTMENT_NAME, t1_.LOCATION, t1_.VERSION
from EMPLOYEE t0_ inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)
where t1_.DEPARTMENT_NAME = ?

Associating Multiple Entities:

Employee_ e = new Employee_();
Department_ d = new Department_();
Address_ a = new Address_();

List<Employee> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .innerJoin(a, on -> on.eq(e.addressId, a.addressId))
    .where(c -> c.eq(d.departmentName, "SALES"))
    .associate(
        e,
        d,
        (employee, department) -> {
          employee.setDepartment(department);
          department.getEmployeeList().add(employee);
        })
    .associate(e, a, Employee::setAddress)
    .fetch();

Associating Immutable Entities

To associate immutable entities, use the associateWith operation with a join expression:

Emp_ e = new Emp_();
Emp_ m = new Emp_();
Dept_ d = new Dept_();

List<Emp> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .leftJoin(m, on -> on.eq(e.managerId, m.employeeId))
    .where(c -> c.eq(d.departmentName, "SALES"))
    .associateWith(e, d, Emp::withDept)
    .associateWith(e, m, Emp::withManager)
    .fetch();

This query generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION,
t1_.DEPARTMENT_ID, t1_.DEPARTMENT_NO, t1_.DEPARTMENT_NAME, t1_.LOCATION, t1_.VERSION,
t2_.EMPLOYEE_ID, t2_.EMPLOYEE_NO, t2_.EMPLOYEE_NAME, t2_.MANAGER_ID, t2_.HIREDATE,
t2_.SALARY, t2_.DEPARTMENT_ID, t2_.ADDRESS_ID, t2_.VERSION
from EMPLOYEE t0_
inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)
left outer join EMPLOYEE t2_ on (t0_.MANAGER_ID = t2_.EMPLOYEE_ID)
where t1_.DEPARTMENT_NAME = ?

Dynamic Join Expression

A join expression uses only evaluated operators to build a JOIN clause. When no operators are evaluated, the JOIN clause is omitted.

For example, with a conditional join:

Employee_ e = new Employee_();
Employee_ e2 = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .innerJoin(e2, on -> {
        if (join) {
            on.eq(e.managerId, e2.employeeId);
        }
    })
    .fetch();

If join is false, the on expression is ignored, generating:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_

Dynamic Association

With dynamic join expressions, associations can be made optional. Use AssociationOption.optional() in the associate method:

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

List<Employee> list = queryDsl
    .from(e)
    .innerJoin(d, on -> {
        if (join) {
            on.eq(e.departmentId, d.departmentId);
        }
    })
    .associate(
        e,
        d,
        (employee, department) -> {
          employee.setDepartment(department);
          department.getEmployeeList().add(employee);
        },
        AssociationOption.optional())
    .fetch();

Aggregate Functions

The following aggregate functions are supported:

  • avg(property)

  • avgAsDouble(property)

  • count()

  • count(property)

  • countDistinct(property)

  • max(property)

  • min(property)

  • sum(property)

These functions are defined in the org.seasar.doma.jdbc.criteria.expression.Expressions class and can be used with static imports.

For example, to pass the sum function to the select method:

Employee_ e = new Employee_();

Salary salary = queryDsl.from(e).select(sum(e.salary)).fetchOne();

This generates:

select sum(t0_.SALARY) from EMPLOYEE t0_

Group By Expression

Group by expressions allow for grouping results based on specified columns:

Employee_ e = new Employee_();

List<Tuple2<Integer, Long>> list = queryDsl
    .from(e)
    .groupBy(e.departmentId)
    .select(e.departmentId, count())
    .fetch();

The above code generates:

select t0_.DEPARTMENT_ID, count(*) from EMPLOYEE t0_ group by t0_.DEPARTMENT_ID

When a group by expression is not specified, the expression is inferred from the select expression automatically. Thus, the following code issues the same SQL as above:

Employee_ e = new Employee_();

List<Tuple2<Integer, Long>> list = queryDsl.from(e).select(e.departmentId, count()).fetch();

Having Expression

The following operators are supported in having expressions:

  • eq - (=)

  • ne - (<>)

  • ge - (>=)

  • gt - (>)

  • le - (<=)

  • lt - (<)

Logical operators are also supported:

  • and

  • or

  • not

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

List<Tuple2<Long, String>> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .having(c -> c.gt(count(), 3L))
    .orderBy(c -> c.asc(count()))
    .select(count(), d.departmentName)
    .fetch();

The above query generates:

select count(*), t1_.DEPARTMENT_NAME
from EMPLOYEE t0_
inner join DEPARTMENT t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)
group by t1_.DEPARTMENT_NAME having count(*) > ?
order by count(*) asc

Dynamic Having Expression

A having expression includes only evaluated operators, omitting the HAVING clause if no operators are evaluated.

For instance, a conditional expression in a having clause:

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

List<Tuple2<Long, String>> list = queryDsl
    .from(e)
    .innerJoin(d, on -> on.eq(e.departmentId, d.departmentId))
    .groupBy(d.departmentName)
    .having(c -> {
        if (countCondition) {
            c.gt(count(), 3L);
        }
    })
    .select(count(), d.departmentName)
    .fetch();

If countCondition is false, the having clause is ignored in the SQL statement.

Order By Expression

Supported ordering operations are:

  • asc

  • desc

Employee_ e = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .orderBy(c -> {
        c.asc(e.departmentId);
        c.desc(e.salary);
    })
    .fetch();

The query above generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
order by t0_.DEPARTMENT_ID asc, t0_.SALARY desc

Dynamic Order By Expression

Order by expressions use only evaluated operators to build the ORDER BY clause. When no operators are evaluated, the ORDER BY clause is omitted.

Distinct Expression

To select distinct rows, use distinct():

List<Department> list = queryDsl
    .from(d)
    .distinct()
    .leftJoin(e, on -> on.eq(d.departmentId, e.departmentId))
    .fetch();

This query generates:

select distinct t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME,
t0_.LOCATION, t0_.VERSION
from DEPARTMENT t0_
left outer join EMPLOYEE t1_ on (t0_.DEPARTMENT_ID = t1_.DEPARTMENT_ID)

Limit and Offset Expression

To limit the number of rows and specify an offset:

Employee_ e = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .limit(5)
    .offset(3)
    .orderBy(c -> c.asc(e.employeeNo))
    .fetch();

This generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
order by t0_.EMPLOYEE_NO asc
offset 3 rows fetch first 5 rows only

Dynamic Limit and Offset Expression

Limit and offset expressions include only non-null values in the SQL. If either value is null, the corresponding FETCH FIRST or OFFSET clause is omitted.

For Update Expression

The forUpdate method allows row locking in SQL:

Employee_ e = new Employee_();

List<Employee> list = queryDsl
    .from(e)
    .where(c -> c.eq(e.employeeId, 1))
    .forUpdate()
    .fetch();

The query above generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
where t0_.EMPLOYEE_ID = ?
for update

Union Expression

Supported union operations include:

  • union

  • unionAll - (union all)

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

List<Tuple2<Integer, String>> list = queryDsl
    .from(e)
    .select(e.employeeId, e.employeeName)
    .union(queryDsl.from(d)
        .select(d.departmentId, d.departmentName))
    .fetch();

This generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME from EMPLOYEE t0_
union
select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NAME from DEPARTMENT t0_

Using order by with an index in union queries:

List<Tuple2<Integer, String>> list = queryDsl
    .from(e)
    .select(e.employeeId, e.employeeName)
    .union(queryDsl.from(d)
        .select(d.departmentId, d.departmentName))
    .orderBy(c -> c.asc(2))
    .fetch();

Derived Table Expression

Subqueries using derived tables are supported. A corresponding entity class for the derived table is required.

Define the entity class for the derived table as follows:

@Entity(metamodel = @Metamodel)
public class NameAndAmount {
  private String name;
  private Integer amount;

  public NameAndAmount() {}

  public NameAndAmount(String accounting, BigDecimal bigDecimal) {
    this.name = accounting;
    this.amount = bigDecimal.intValue();
  }

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }
  public Integer getAmount() { return amount; }
  public void setAmount(Integer amount) { this.amount = amount; }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    NameAndAmount that = (NameAndAmount) o;
    return Objects.equals(name, that.name) && Objects.equals(amount, that.amount);
  }

  @Override
  public int hashCode() { return Objects.hash(name, amount); }
}

A subquery using a derived table can be written as follows:

Department_ d = new Department_();
Employee_ e = new Employee_();
NameAndAmount_ t = new NameAndAmount_();

SetOperand<?> subquery = queryDsl
    .from(e)
    .innerJoin(d, c -> c.eq(e.departmentId, d.departmentId))
    .groupBy(d.departmentName)
    .select(d.departmentName, Expressions.sum(e.salary));

List<NameAndAmount> list = queryDsl
    .from(t, subquery)
    .orderBy(c -> c.asc(t.name))
    .fetch();

This generates:

select
    t0_.NAME,
    t0_.AMOUNT
from
    (
        select
            t2_.DEPARTMENT_NAME AS NAME,
            sum(t1_.SALARY) AS AMOUNT
        from
            EMPLOYEE t1_
        inner join
            DEPARTMENT t2_ on (t1_.DEPARTMENT_ID = t2_.DEPARTMENT_ID)
        group by
            t2_.DEPARTMENT_NAME
    ) t0_
order by
    t0_.NAME asc

Delete Statement

The delete statement follows the same rules as the Where Expression.

Delete Settings

The following settings are supported:

  • allowEmptyWhere

  • batchSize

  • comment

  • ignoreVersion

  • queryTimeout

  • sqlLogType

  • suppressOptimisticLockException

All are optional and can be applied as follows:

Employee_ e = new Employee_();

int count = queryDsl.delete(e, settings -> {
  settings.setAllowEmptyWhere(true);
  settings.setBatchSize(20);
  settings.setComment("delete all");
  settings.setIgnoreVersion(true);
  settings.setQueryTimeout(1000);
  settings.setSqlLogType(SqlLogType.RAW);
  settings.setSuppressOptimisticLockException(true);
})
.where(c -> {})
.execute();

Note

To allow a delete statement with an empty WHERE clause, enable the allowEmptyWhere setting.

Delete Record by Entity

Employee_ e = new Employee_();

Employee employee = queryDsl.from(e).where(c -> c.eq(e.employeeId, 5)).fetchOne();

Result<Employee> result = queryDsl.delete(e).single(employee).execute();

This generates:

delete from EMPLOYEE where EMPLOYEE_ID = ? and VERSION = ?

Batch Delete is also supported:

List<Employee> employees = queryDsl.from(e).where(c -> c.in(e.employeeId, Arrays.asList(5, 6))).fetch();

BatchResult<Employee> result = queryDsl.delete(e).batch(employees).execute();

Exceptions thrown by the execute method include:

  • OptimisticLockException: if the entity has a version property and an update count is 0

Delete Records by Where Expression

To delete by a condition:

int count = queryDsl.delete(e).where(c -> c.ge(e.salary, new Salary("2000"))).execute();

This generates:

delete from EMPLOYEE t0_ where t0_.SALARY >= ?

To delete all records, use the all method:

int count = queryDsl.delete(e).all().execute();

Insert Statement

Insert Settings

Supported insert settings include:

  • comment

  • queryTimeout

  • sqlLogType

  • batchSize

  • excludeNull

  • include

  • exclude

  • ignoreGeneratedKeys

All are optional and can be applied as follows:

Department_ d = new Department_();

int count = queryDsl.insert(d, settings -> {
    settings.setComment("insert department");
    settings.setQueryTimeout(1000);
    settings.setSqlLogType(SqlLogType.RAW);
    settings.setBatchSize(20);
    settings.excludeNull(true);
})
.values(c -> {
    c.value(d.departmentId, 99);
    c.value(d.departmentNo, 99);
    c.value(d.departmentName, "aaa");
    c.value(d.location, "bbb");
    c.value(d.version, 1);
})
.execute();

You can specify excluded columns:

Department department = ...;

Result<Department> result = queryDsl.insert(d, settings ->
    settings.exclude(d.departmentName, d.location)
).single(department).execute();

Insert Record with Entity

Inserting a single entity:

Department department = new Department();
department.setDepartmentId(99);
department.setDepartmentNo(99);
department.setDepartmentName("aaa");
department.setLocation("bbb");

Result<Department> result = queryDsl.insert(d).single(department).execute();

This generates:

insert into DEPARTMENT (DEPARTMENT_ID, DEPARTMENT_NO, DEPARTMENT_NAME, LOCATION, VERSION)
values (?, ?, ?, ?, ?)

Batch Insert is also supported:

Department department = ...;
Department department2 = ...;
List<Department> departments = Arrays.asList(department, department2);

BatchResult<Department> result = queryDsl.insert(d).batch(departments).execute();

Multi-row Insert is also supported:

MultiResult<Department> result = queryDsl.insert(d).multi(departments).execute();

This generates:

insert into DEPARTMENT (DEPARTMENT_ID, DEPARTMENT_NO, DEPARTMENT_NAME, LOCATION, VERSION)
values (?, ?, ?, ?, ?), (?, ?, ?, ?, ?)

Upsert is supported as well, with options to handle duplicate keys:

To update on duplicate key:

BatchResult<Department> = queryDsl
    .insert(d)
    .multi(departments)
    .onDuplicateKeyUpdate()
    .execute();

To ignore duplicates:

BatchResult<Department> = queryDsl
    .insert(d)
    .multi(departments)
    .onDuplicateKeyIgnore()
    .execute();

Exceptions include:

  • UniqueConstraintException: if a unique constraint is violated.

Insert Record with Specified Values

Inserting records by specifying values:

int count = queryDsl.insert(d)
    .values(c -> {
        c.value(d.departmentId, 99);
        c.value(d.departmentNo, 99);
        c.value(d.departmentName, "aaa");
        c.value(d.location, "bbb");
        c.value(d.version, 1);
    })
    .execute();

This generates:

insert into DEPARTMENT (DEPARTMENT_ID, DEPARTMENT_NO, DEPARTMENT_NAME, LOCATION, VERSION)
values (?, ?, ?, ?, ?)

Unique constraints may throw:

  • UniqueConstraintException: if a unique constraint is violated.

We also support the INSERT SELECT syntax:

Department_ da = new Department_("DEPARTMENT_ARCHIVE");
Department_ d = new Department_();

int count = queryDsl.insert(da)
    .select(c -> c.from(d).where(cc -> cc.in(d.departmentId, Arrays.asList(1, 2))))
    .execute();

This generates:

insert into DEPARTMENT_ARCHIVE (DEPARTMENT_ID, DEPARTMENT_NO, DEPARTMENT_NAME,
LOCATION, VERSION) select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME,
t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_ where t0_.DEPARTMENT_ID in (?, ?)

For upserts, specify keys and update values on duplicates:

int count result = queryDsl
    .insert(d)
    .values(c -> {
        c.value(d.departmentId, 1);
        c.value(d.departmentNo, 60);
        c.value(d.departmentName, "DEVELOPMENT");
        c.value(d.location, "KYOTO");
        c.value(d.version, 2);
    })
    .onDuplicateKeyUpdate()
    .keys(d.departmentId)
    .set(c -> {
        c.value(d.departmentName, c.excluded(d.departmentName));
        c.value(d.location, "KYOTO");
        c.value(d.version, 3);
    })
    .execute();

To ignore duplicates and specify keys:

int count result = queryDsl
    .insert(d)
    .values(c -> {
        c.value(d.departmentId, 1);
        c.value(d.departmentNo, 60);
        c.value(d.departmentName, "DEVELOPMENT");
        c.value(d.location, "KYOTO");
        c.value(d.version, 2);
    })
    .onDuplicateKeyIgnore()
    .keys(d.departmentId)
    .execute();

Update Statement

The update statement follows the same specifications as the Where Expression.

Update Settings

The following settings are supported:

  • allowEmptyWhere

  • batchSize

  • comment

  • ignoreVersion

  • queryTimeout

  • sqlLogType

  • suppressOptimisticLockException

  • excludeNull

  • include

  • exclude

All are optional and can be applied as follows:

Employee_ e = new Employee_();

int count = queryDsl.update(e, settings -> {
  settings.setAllowEmptyWhere(true);
  settings.setBatchSize(20);
  settings.setComment("update all");
  settings.setIgnoreVersion(true);
  settings.setQueryTimeout(1000);
  settings.setSqlLogType(SqlLogType.RAW);
  settings.setSuppressOptimisticLockException(true);
  settings.excludeNull(true);
}).set(c -> {
  c.value(e.employeeName, "aaa");
}).execute();

You can also specify excluded columns:

Employee employee = ...;

Result<Employee> result = queryDsl.update(e, settings ->
    settings.exclude(e.hiredate, e.salary)
).single(employee).execute();

Note

To perform an update without a WHERE clause, enable the allowEmptyWhere setting.

Update Record by Entity

Updating a single entity:

Employee employee = queryDsl.from(e).where(c -> c.eq(e.employeeId, 5)).fetchOne();
employee.setEmployeeName("aaa");
employee.setSalary(new Salary("2000"));

Result<Employee> result = queryDsl.update(e).single(employee).execute();

This generates:

update EMPLOYEE set EMPLOYEE_NAME = ?, SALARY = ?, VERSION = ? + 1
where EMPLOYEE_ID = ? and VERSION = ?

Batch Update is also supported:

Employee employee = ...;
Employee employee2 = ...;
List<Employee> employees = Arrays.asList(employee, employee2);

BatchResult<Employee> result = queryDsl.update(e).batch(employees).execute();

Exceptions from the execute method may include:

  • OptimisticLockException: if the entity has a version property and the update count is 0

  • UniqueConstraintException: if a unique constraint is violated

Update Records by Where Expression

To update records based on a condition:

int count = queryDsl.update(e)
    .set(c -> c.value(e.departmentId, 3))
    .where(c -> {
        c.isNotNull(e.managerId);
        c.ge(e.salary, new Salary("2000"));
    })
    .execute();

This generates:

update EMPLOYEE t0_ set t0_.DEPARTMENT_ID = ?
where t0_.MANAGER_ID is not null and t0_.SALARY >= ?

Exceptions may include:

  • UniqueConstraintException: if a unique constraint is violated

Property Expressions

All property expression methods are in the org.seasar.doma.jdbc.criteria.expression.Expressions class and can be used with static imports.

Arithmetic Expressions

The following methods are available for arithmetic expressions:

  • add - (+)

  • sub - (-)

  • mul - (*)

  • div - (/)

  • mod - (%)

Example of using the add method:

int count = queryDsl.update(e)
    .set(c -> c.value(e.version, add(e.version, 10)))
    .where(c -> c.eq(e.employeeId, 1))
    .execute();

This generates:

update EMPLOYEE t0_
set t0_.VERSION = (t0_.VERSION + ?)
where t0_.EMPLOYEE_ID = ?

String Functions

The following string functions are provided:

  • concat

  • lower

  • upper

  • trim

  • ltrim

  • rtrim

Example using concat:

int count = queryDsl.update(e)
    .set(c -> c.value(e.employeeName, concat("[", concat(e.employeeName, "]"))))
    .where(c -> c.eq(e.employeeId, 1))
    .execute();

This generates:

update EMPLOYEE t0_
set t0_.EMPLOYEE_NAME = concat(?, concat(t0_.EMPLOYEE_NAME, ?))
where t0_.EMPLOYEE_ID = ?

Literal Expression

The literal method supports all basic data types.

Example of using literal:

Employee employee = queryDsl.from(e)
    .where(c -> c.eq(e.employeeId, literal(1)))
    .fetchOne();

This generates:

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NO, t0_.EMPLOYEE_NAME, t0_.MANAGER_ID, t0_.HIREDATE,
t0_.SALARY, t0_.DEPARTMENT_ID, t0_.ADDRESS_ID, t0_.VERSION
from EMPLOYEE t0_
where t0_.EMPLOYEE_ID = 1

Note

Literal expressions are not recognized as bind variables.

Case Expression

The following method is supported for case expressions:

  • when

Example of using when:

List<String> list = queryDsl
    .from(e)
    .select(
        when(c -> {
            c.eq(e.employeeName, literal("SMITH"), lower(e.employeeName));
            c.eq(e.employeeName, literal("KING"), lower(e.employeeName));
        }, literal("_")))
    .fetch();

This generates:

select case
        when t0_.EMPLOYEE_NAME = 'SMITH' then lower(t0_.EMPLOYEE_NAME)
        when t0_.EMPLOYEE_NAME = 'KING' then lower(t0_.EMPLOYEE_NAME)
        else '_' end
from EMPLOYEE t0_

Subquery Select Expression

The select method supports subquery select expressions.

Example usage:

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

SelectExpression<Salary> subSelect = select(c ->
    c.from(e2)
     .innerJoin(d, on -> on.eq(e2.departmentId, d.departmentId))
     .where(cc -> cc.eq(e.departmentId, d.departmentId))
     .groupBy(d.departmentId)
     .select(max(e2.salary))
);

int count = queryDsl.update(e)
    .set(c -> c.value(e.salary, subSelect))
    .where(c -> c.eq(e.employeeId, 1))
    .execute();

This generates:

update EMPLOYEE t0_
set t0_.SALARY = (
    select max(t1_.SALARY)
    from EMPLOYEE t1_
    inner join DEPARTMENT t2_ on (t1_.DEPARTMENT_ID = t2_.DEPARTMENT_ID)
    where t0_.DEPARTMENT_ID = t2_.DEPARTMENT_ID
    group by t2_.DEPARTMENT_ID
)
where t0_.EMPLOYEE_ID = ?

User-Defined Expressions

You can define user-defined expressions using Expressions.userDefined.

Example of defining a custom replace function:

UserDefinedExpression<String> replace(PropertyMetamodel<String> expression, PropertyMetamodel<String> from, PropertyMetamodel<String> to) {
    return Expressions.userDefined(expression, "replace", from, to, c -> {
        c.appendSql("replace(");
        c.appendExpression(expression);
        c.appendSql(", ");
        c.appendExpression(from);
        c.appendSql(", ");
        c.appendExpression(to);
        c.appendSql(")");
    });
}

Using the custom replace function in a query:

List<String> list = queryDsl
    .from(d)
    .select(replace(d.location, Expressions.literal("NEW"), Expressions.literal("new")))
    .fetch();

This generates:

select replace(t0_.LOCATION, 'NEW', 'new') from DEPARTMENT t0_

Scopes

Scopes allow you to specify commonly-used query conditions.

To define a scope, create a class with a method annotated with @Scope:

public class DepartmentScope {
    @Scope
    public Consumer<WhereDeclaration> onlyTokyo(Department_ d) {
        return c -> c.eq(d.location, "Tokyo");
    }
}

To enable the scope, specify the scope class in the scopes element of @Metamodel:

@Entity(metamodel = @Metamodel(scopes = { DepartmentScope.class }))
public class Department { ... }

Now Department_ includes the onlyTokyo method, which can be used as follows:

List<Department> list = queryDsl.from(d).where(d.onlyTokyo()).fetch();

This generates:

select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_
where t0_.LOCATION = ?

To combine other query conditions with scopes, use the andThen method:

List<Department> list = queryDsl
    .from(d)
    .where(d.onlyTokyo().andThen(c -> c.gt(d.departmentNo, 50)))
    .fetch();

Defining multiple scopes within a class:

public class DepartmentScope {
    @Scope
    public Consumer<WhereDeclaration> onlyTokyo(Department_ d) {
        return c -> c.eq(d.location, "Tokyo");
    }

    @Scope
    public Consumer<WhereDeclaration> locationStartsWith(Department_ d, String prefix) {
        return c -> c.like(d.location, prefix, LikeOption.prefix());
    }

    @Scope
    public Consumer<OrderByNameDeclaration> sortByNo(Department_ d) {
        return c -> c.asc(d.departmentNo);
    }
}

Tips

Execution in DAO

It can be useful to execute DSLs within a default method of the DAO interface. To obtain a config object, call Config.get(this) within the default method:

@Dao
public interface EmployeeDao {

  default Optional<Employee> selectById(Integer id) {
    QueryDsl queryDsl = new QueryDsl(Config.get(this));

    Employee_ e = new Employee_();
    return queryDsl.from(e).where(c -> c.eq(e.employeeId, id)).fetchOptional();
  }
}

Overwriting the Table Name

A metamodel constructor can accept a qualified table name, which allows the metamodel to overwrite its default table name.

This feature is useful for working with two tables that share the same structure:

Department_ da = new Department_("DEPARTMENT_ARCHIVE");
Department_ d = new Department_();

int count = queryDsl
    .insert(da)
    .select(c -> c.from(d))
    .execute();

This generates:

insert into DEPARTMENT_ARCHIVE (DEPARTMENT_ID, DEPARTMENT_NO, DEPARTMENT_NAME,
LOCATION, VERSION) select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME,
t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_

Debugging

To inspect the SQL statement generated by DSLs, use the asSql method:

Department_ d = new Department_();

Listable<Department> stmt = queryDsl.from(d).where(c -> c.eq(d.departmentName, "SALES"));

Sql<?> sql = stmt.asSql();
System.out.printf("Raw SQL      : %s\n", sql.getRawSql());
System.out.printf("Formatted SQL: %s\n", sql.getFormattedSql());

The code above outputs the following:

Raw SQL      : select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_ where t0_.DEPARTMENT_NAME = ?
Formatted SQL: select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_ where t0_.DEPARTMENT_NAME = 'SALES'

The asSql method does not execute the SQL statement against the database; it only builds the SQL statement and returns it as an Sql object.

You can also obtain the Sql object by using the peek method:

List<String> locations = queryDsl
    .from(d)
    .peek(System.out::println)
    .where(c -> c.eq(d.departmentName, "SALES"))
    .peek(System.out::println)
    .orderBy(c -> c.asc(d.location))
    .peek(sql -> System.out.println(sql.getFormattedSql()))
    .select(d.location)
    .peek(sql -> System.out.println(sql.getFormattedSql()))
    .fetch();

The code above outputs SQL statements at various stages of the query:

select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_
select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_ where t0_.DEPARTMENT_NAME = ?
select t0_.DEPARTMENT_ID, t0_.DEPARTMENT_NO, t0_.DEPARTMENT_NAME, t0_.LOCATION, t0_.VERSION from DEPARTMENT t0_ where t0_.DEPARTMENT_NAME = 'SALES' order by t0_.LOCATION asc
select t0_.LOCATION from DEPARTMENT t0_ where t0_.DEPARTMENT_NAME = 'SALES' order by t0_.LOCATION asc

Sample Projects

You can refer to the following sample projects for additional guidance: