Classic Criteria API

イントロダクション

警告

ここで説明する Entityql と NativeSql DSL の代わりに Unified Criteria API のページで紹介する Query DSL を使用してください。 Query DSLは、EntityqlとNativeSql DSLの両方を組み合わせた新しい統合インターフェイスです。

Criteria API には 2 種類の DSL があります。

  • Entityql DSL

  • NativeSql DSL

どちらも、事前定義されたエンティティクラスとメタモデルクラスを必要とします。

次のエンティティクラスを使用して、いくつかの例を示します。

@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
}

上記のクラスには @Entity(metamodel = @Metamodel) というアノテーションが付けられていることに注意してください。 metamodel = @Metamodel は、エンティティクラスに対応するメタモデルクラスをDoma の アノテーションプロセッサー により生成することを示します。

この例では、メタモデルクラスは Employee_Department_Emp_、および Dept_ です。これらのメタモデルクラスを使用すると、クエリをタイプセーフに構築できます。

メタモデルの名前は、メタモデルのアノテーション要素によってカスタマイズできます。

すべてのメタモデルを一括でカスタマイズするには、アノテーションプロセッサのオプションを使用できます。 アノテーションプロセッシング を参照して、次のオプションを確認してください。

  • doma.metamodel.enabled

  • doma.metamodel.prefix

  • doma.metamodel.suffix

Entityql DSL

Entityql DSL は、エンティティをクエリして関連付けることができます。エントリポイントは org.seasar.doma.jdbc.criteria.Entityql クラスです。このクラスには次のメソッドがあります。

  • from

  • insert

  • delete

  • update

次のように Entityql クラスをインスタンス化できます。

Entityql entityql = new Entityql(config);

たとえば、 Employee エンティティと Department エンティティをクエリしてそれらを関連付けるには、次のように記述します。

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

List<Employee> list =
    entityql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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 = ?

NativeSql DSL

NativeSql DSL は、Entityql DSL よりも複雑な SQL ステートメントを発行できます。ただし、NativeSql DSL はエンティティの関連付けをサポートしていないことに注意してください。

エントリポイントは org.seasar.doma.jdbc.criteria.NativeSql クラスです。このクラスには次のメソッドがあります。

  • from

  • delete

  • insert

  • update

次のように NativeSql クラスをインスタンス化できます。

NativeSql nativeSql = new NativeSql(config);

たとえば、GROUP BY 句と HAVING 句を使用して 2 つの列をクエリするには、次のように記述します。

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

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

上記のクエリは次の SQL ステートメントを発行します。

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

2 つの DSL の違い

2 つの DSL の最大の違いは、Entityql DSL はフェッチされた結果から重複データを削除しますが、NativeSQL DSL は削除しないことです。

次の例を参照してください。

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

// (1) Use Entityql DSL
List<Department> list1 =
    entityql.from(d).innerJoin(e, on -> on.eq(d.departmentId, e.departmentId)).fetch();

// (2) Use NativeSql DSL
List<Department> list2 =
    nativeSql.from(d).innerJoin(e, on -> on.eq(d.departmentId, e.departmentId)).fetch();

System.out.println(list1.size()); //  3
System.out.println(list2.size()); // 14

Both (1) and (2) issue the same SQL statement as follows:

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

ResultSet には 14 行が含まれていますが、Entityql DSL は重複するDepartment エンティティを削除するため、3 行のみを返します。 Entityql DSL は、id プロパティを使用してエンティティの一意性を認識します。

一方、NativeSql DSL は、データベースから取得したデータを返します。結果はエンティティオブジェクトに格納されますが、それらはプレーンな DTO として処理されます。

Select ステートメント

Select 設定 (Entityql, NativeSql)

次の設定をサポートしています。

  • allowEmptyWhere

  • comment

  • fetchSize

  • maxRows

  • queryTimeout

  • sqlLogType

これらはすべてオプションです。それらは次のように適用できます。

Employee_ e = new Employee_();

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

フェッチ (Entityql、NativeSql)

Entityql DSL と NativeSql DSL はどちらも、データベースからデータをフェッチする次のメソッドをサポートしています。

  • fetch

  • fetchOne

  • fetchOptional

  • stream

Employee_ e = new Employee_();

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

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

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

// The stream method returns results as a stream.
// The following code is equivalent to "entityql.from(e).fetch().stream()"
Stream<Employee> stream =
    entityql.from(e).stream();

ストリーミング (NativeSql)

NativeSql DSL は次のメソッドをサポートしています。

  • mapStream

  • collect

  • openStream

Employee_ e = new Employee_();

// The mapStream method handles a stream.
Map<Integer, List<Employee>> map =
    nativeSql
        .from(e)
        .mapStream(stream -> stream.collect(groupingBy(Employee::getDepartmentId)));

// The collect method is a shortcut of the mapStream method.
// The following code does the same thing with the above.
Map<Integer, List<Employee>> map2 =
    nativeSql.from(e).collect(groupingBy(Employee::getDepartmentId));

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

これらのメソッドは、JDBC ResultSet をラップするストリームを処理します。したがって、これらは大規模な ResultSet を効率的に処理するのに役立ちます。

Select 式

エンティティの検索 (Entityql、NativeSql)

デフォルトでは、結果のエンティティタイプは from メソッドで指定されたものと同じです。次のコードを参照してください。

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

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

上記のクエリは次の SQL ステートメントを発行します。

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)

結合されたエンティティの型を結果のエンティティの型として選択するには、次のように select メソッドを呼び出します。

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

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

上記のクエリは次の 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)

複数のエンティティの検索 (NativeSql)

次のように、複数のエンティティの型を指定してタプルとしてフェッチできます。

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

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

上記のクエリは次の SQL ステートメントを発行します。

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 = ?

エンティティのすべてのプロパティが null の場合、タプルに含まれるエンティティは null になる可能性があります。

カラムの射影 (NativeSql)

カラムを射影するには、 select メソッドを使用します。

1 つのカラムを射影するには、次のように 1 つのプロパティを select メソッドに渡します。

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

select t0_.EMPLOYEE_NAME from EMPLOYEE t0_

2 つ以上のカラムを射影するには、次のように 2 つ以上のプロパティを select メソッドに渡します。

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

select t0_.EMPLOYEE_NAME, t0_.EMPLOYEE_NO from EMPLOYEE t0_

9 個までのカラムの結果は Tuple2 から Tuple9 に保持されます。 9 個を超える数値の場合、結果は Row によって保持されます。

次のように selectAsRow を使用すると、 Row リストを明示的に取得できます。

Employee_ e = new Employee_();

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

カラムの射影とマッピング (Entityql、NativeSql)

カラムを射影してエンティティにマップするには、次のように selectTo メソッドを使用します。

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

select t0_.EMPLOYEE_ID, t0_.EMPLOYEE_NAME from EMPLOYEE t0_

上記の SQL ステートメントの SELECT 句には主キー EMPLOYEE_ID が含まれていることに注意してください。 selectTo メソッドにエンティティの id プロパティが含まれていない場合でも、SELECT 句には常に主キーが含まれます。

Where 式 (Entityql、NativeSql)

次の演算子と述語がサポートされています。

  • 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)

注釈

右側のオペランドが null の場合、WHERE 句または HAVING 句には演算子が含まれません。詳細については、 WhereDeclaration および HavingDeclaration の javadoc を参照してください。

次のユーティリティ演算子もサポートしています。

  • eqOrIsNull - ("=" or "is null")

  • neOrIsNotNull - ("<>" or "is not null")

次の論理演算子もサポートしています。

  • and

  • or

  • not

Employee_ e = new Employee_();

List<Employee> list =
    entityql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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 < ?)

サブクエリは次のように記述できます。

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

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

上記のクエリは次の SQL ステートメントを発行します。

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

動的 where 式 (Entityql、NativeSql)

where 式は、評価された演算子のみを使用して WHERE 句を構築します。

where 式ですべての演算子が評価されない場合、構築されたステートメントには WHERE 句が含まれません。

また、すべての演算子が論理演算子の式で評価されない場合、構築されたステートメントには論理演算子の式が含まれません。

たとえば、where 式に次のような条件式が含まれているとします。

Employee_ e = new Employee_();

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

enableNameCondition 変数が false の場合、like 式は無視されます。上記のクエリは次の SQL ステートメントを発行します。

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 式

次の式がサポートされています。

  • innerJoin - (内部結合)

  • leftJoin - (左外部結合)

innerJoin (Entityql、NativeSql)

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

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

上記のクエリは次の SQL ステートメントを発行します。

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)

leftJoin (Entityql、NativeSQL)

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

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

上記のクエリは次の SQL ステートメントを発行します。

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)

関連付け (Entityql)

Entityql DSL の associate メソッドを使用してエンティティを関連付けることができます。 join 式と一緒に associate メソッドを使用する必要があります。

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

List<Employee> list =
    entityql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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 = ?

多くのエンティティを関連付けることができます。

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

List<Employee> list =
    entityql
        .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();

不変エンティティの関連付け (Entityql)

不変エンティティを Entityql DSL の associateWith メソッドに関連付けることができます。 join 式と一緒に associateWith メソッドを使用する必要があります。

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

List<Emp> list =
    entityql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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 = ?

動的 join 式 (Entityql、NativeSql)

join 式は、評価された演算子のみを使用して JOIN 句を構築します。

すべての演算子が join 式で評価されない場合、構築されたステートメントには JOIN 句は含まれません。

たとえば、 join 式に条件分岐が含まれているとします。

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

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

join 変数が false の場合、on 式は無視されます。上記のクエリは次の SQL ステートメントを発行します。

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_

動的関連付け (Entityql)

上記の動的 join 式を使用する場合、関連付けはオプションである必要があります。これを行うには、 AssociationOption.optional() の結果を関連付けメソッドに渡します。

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

List<Employee> list =
    entityql
        .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();

集約関数 (NativeSql)

次の集約関数をサポートしています。

  • avg(property)

  • avgAsDouble(property)

  • count()

  • count(property)

  • countDistinct(property)

  • max(property)

  • min(property)

  • sum(property)

これらは org.seasar.doma.jdbc.criteria.expression.Expressions クラスで定義されています。静的インポートで使用します。

たとえば、 sum 関数を select メソッドに渡すことができます。

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

select sum(t0_.SALARY) from EMPLOYEE t0_

Group by 式 (NativeSql)

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

group by 式を指定しない場合、group by 式は select 式から自動的に推測されます。したがって、次のコードは上記と同じ SQL ステートメントを発行します。

Employee_ e = new Employee_();

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

Having 式(NativeSql)

次の演算子をサポートしています。

  • eq - (=)

  • ne - (<>)

  • ge - (>=)

  • gt - (>)

  • le - (<=)

  • lt - (<)

次の論理演算子もサポートしています。

  • and

  • or

  • not

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

List<Tuple2<Long, String>> list =
    nativeSql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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(*) > ? or (min(t0_.SALARY) <= ?)
order by count(*) asc

動的 Having 式 (NativeSql)

having 式では、評価された演算子のみを使用して HAVING 句を構築します。

having 式ですべての演算子が評価されない場合、構築されたステートメントには HAVING 句は含まれません。

また、すべての演算子が論理演算子の式で評価されない場合、構築されたステートメントには論理演算子の式が含まれません。

Order by 式 (Entityql、NativeSql)

次のソート操作をサポートしています。

  • asc

  • desc

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

動的 Order by 式 (NativeSql)

order by 式では、評価された演算子のみを使用して ORDER BY 句を作成します。

すべての演算子が order by 式で評価されない場合、構築されたステートメントには ORDER BY 句は含まれません。

Distinct 式 (Entityql、NativeSql)

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

上記のクエリは次の SQL ステートメントを発行します。

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 および offset 式 (Entityql、NativeSql)

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

動的な limit および offset 式 (Entityql、NativeSql)

limit 式では、null 以外の値のみを使用して FETCH FIRST 句を構築します。値が null の場合、構築されたステートメントには FETCH FIRST 句がありません。

また、offset 式では、OFFSET 句を構築するために null 以外の値のみが使用されます。値が null の場合、構築されたステートメントには OFFSET 句がありません。

For update 式 (Entityql、NativeSql)

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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 式(NativeSql)

次の式がサポートされています。

  • union

  • unionAll - (union all)

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

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

上記のクエリは次の SQL ステートメントを発行します。

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

インデックスを使用した式による順序付けがサポートされています。

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

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

派生テーブル式 (Entityql、NativeSql)

派生テーブルを使用したサブクエリをサポートします。ただし、派生テーブルに対応するエンティティクラスが必要です。

派生テーブルに対応するエンティティ クラスを次のように定義します。

@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);
  }
}

派生テーブルを使用したサブクエリは次のように記述できます。

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

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

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

上記のクエリは次の SQL ステートメントを発行します。

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 ステートメント

where 式の仕様については、Where 式 (Entityql、NativeSql) を参照してください。同じルールがステートメントの削除にも適用されます。

Delete 設定(Entityql、NativeSql)

次の設定をサポートしています。

  • allowEmptyWhere

  • batchSize

  • comment

  • ignoreVersion

  • queryTimeout

  • sqlLogType

  • suppressOptimisticLockException

これらはすべてオプションです。

それらは次のように適用できます。

Employee_ e = new Employee_();

int count = nativeSql.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);
}).execute();

注釈

WHERE 句のない削除ステートメントを構築したい場合は、allowEmptyWhere 設定を有効にする必要があります。

Delete ステートメント (Entityql)

Employee_ e = new Employee_();

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

Result<Employee> result = entityql.delete(e, employee).execute();

上記のクエリは次の SQL ステートメントを発行します。

delete from EMPLOYEE where EMPLOYEE_ID = ? and VERSION = ?

バッチ削除もサポートされています。

Employee_ e = new Employee_();

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

BatchResult<Employee> result = entityql.delete(e, employees).execute();

実行メソッドは次の例外をスローする場合があります。

  • OptimisticLockException: エンティティにバージョン プロパティがあり、更新件数が 0 の場合

Delete ステートメント (NativeSql)

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

delete from EMPLOYEE t0_ where t0_.SALARY >= ?

Insert ステートメント

Insert 設定 (Entityql、NativeSql)

次の設定をサポートしています。

  • comment

  • queryTimeout

  • sqlLogType

  • batchSize

  • excludeNull

  • include

  • exclude

  • ignoreGeneratedKeys

これらはすべてオプションです。

それらは次のように適用できます。

Department_ d = new Department_();

int count =
    nativeSql
        .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();
Department_ d = new Department_();

Department department = ...;

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

Insert ステートメント (Entityql)

Department_ d = new Department_();

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

Result<Department> result = entityql.insert(d, department).execute();

上記のクエリは次の SQL ステートメントを発行します。

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

バッチ追加もサポートされています。

Department_ d = new Department_();

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

BatchResult<Department> result = entityql.insert(d, departments).execute();

複数行追加もサポートされています。

Department_ d = new Department_();

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

MultiResult<Department> result = entityql.insertMulti(d, departments).execute();

上記のクエリは次の SQL ステートメントを発行します。

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

Upsertもサポートされています

onDuplicateKeyUpdate を呼び出すことで、キーが重複した場合に更新することができます。

BatchResult<Department> = entityql
    .insert(d, departments)
    .onDuplicateKeyUpdate()
    .execute();

onDuplicateKeyIgnore を呼び出すことで、キーが重複した場合のエラーを無視することができます。

BatchResult<Department> = entityql
    .insert(d, departments)
    .onDuplicateKeyIgnore()
    .execute();

実行メソッドは次の例外をスローする場合があります。

  • UniqueConstraintException: 一意制約に違反した場合

Insert ステートメント (NativeSql)

Department_ d = new Department_();

int count =
    nativeSql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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

実行メソッドは次の例外をスローする場合があります。

  • UniqueConstraintException: 一意制約に違反した場合

次のような INSERT SELECT 構文もサポートされています。

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

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

上記のクエリは次の SQL ステートメントを発行します。

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 (?, ?)

Upsertもサポートされています

onDuplicateKeyUpdate を呼び出すことで、キーが重複したときに更新することができます。 keys で重複チェック対象のキーを指定できます。set で重複した場合の更新の値を指定できます。

int count result = nativeSql
    .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();

onDuplicateKeyIgnore を呼び出すことで、キーが重複したときのエラーを無視することができます。 keys で重複チェック対象のキーを指定できます。

int count result = nativeSql
    .insert(d, departments)
    .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 ステートメント

where 式の仕様については、Where 式 (Entityql、NativeSql) を参照してください。同じルールが更新ステートメントにも適用されます。

Update 設定 (Entityql、NativeSql)

次の設定をサポートしています。

  • allowEmptyWhere

  • batchSize

  • comment

  • ignoreVersion

  • queryTimeout

  • sqlLogType

  • suppressOptimisticLockException

  • excludeNull

  • include

  • exclude

これらはすべてオプションです。

それらは次のように適用できます。

Employee_ e = new Employee_();

int count = nativeSql.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();
Employee_ e = new Employee_();

Employee employee = ...;

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

注釈

WHERE 句のない update ステートメントを構築したい場合は、allowEmptyWhere 設定を有効にする必要があります。

Update ステートメント (Entityql)

Employee_ e = new Employee_();

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

Result<Employee> result = entityql.update(e, employee).execute();

上記のクエリは次の SQL ステートメントを発行します。

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

バッチ更新もサポートされています。

Employee_ e = new Employee_();

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

BatchResult<Employee> result = entityql.update(e, employees).execute();

実行メソッドは次の例外をスローする場合があります。

  • OptimisticLockException: エンティティにバージョン プロパティがあり、更新件数が 0 の場合

  • UniqueConstraintException: 一意制約に違反した場合

Update ステートメント (NativeSql)

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

実行メソッドは次の例外をスローする場合があります。

  • UniqueConstraintException: 一意制約に違反した場合

プロパティ式 (Entityql、NativeSql)

すべての式メソッドは org.seasar.doma.jdbc.criteria.expression.Expressions クラスで定義されています。

静的インポートで使用します。

算術式

次の方法を提供します。

  • add - (+)

  • sub - (-)

  • mul - (*)

  • div - (/)

  • mod - (%)

次のように add メソッドを使用できます。

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

文字列関数

以下の方法を提供します。

  • concat

  • lower

  • upper

  • trim

  • ltrim

  • rtrim

次のように concat メソッドを使用できます。

Employee_ e = new Employee_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

リテラル表現

以下の方法を提供します。

  • literal (for all basic data types)

次のように literal メソッドを使用できます。

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

上記のクエリは次の SQL ステートメントを発行します。

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

注釈

リテラル式はバインド変数として認識されないことに注意してください。

Case 式

次の方法をサポートしています。

  • when

次のように when メソッドを使用できます。

Employee_ e = new Employee_();

List<String> list =
    nativeSql
        .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();

上記のクエリは次の SQL ステートメントを発行します。

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_

サブクエリ select 式

次の方法をサポートしています。

  • select

次のように select メソッドを使用できます。

Employee_ e = new Employee_();

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 =
    nativeSql
        .update(e)
        .set(c -> c.value(e.salary, subSelect))
        .where(c -> c.eq(e.employeeId, 1))
        .execute();

上記のクエリは次の SQL ステートメントを発行します。

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 = ?

ユーザー定義式

Expressions.userDefined を呼び出すことで、ユーザー定義の式を定義できます。

以下の例では、replace 関数が定義されています。

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(")");
    });
}

クエリで replace 関数を使用するには、次のようにします。

Department_ d = new Department_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

スコープ (Entityql、NativeSQL)

スコープを使用すると、一般的に使用されるクエリ条件を指定できます。

単純なスコープを定義するには、 @Scope アノテーションが付けられたメソッドを持つクラスを作成します。

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

スコープを有効にするには、 @Metamodel のscopes 要素に上記のクラスを指定します。

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

上述の設定によりメタモデル Department_ には onlyTokyo メソッドが生成されます。次のように使用できます。

Department_ d = new Department_();

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

上記のクエリは次の SQL ステートメントを発行します。

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

他のクエリ条件をスコープと組み合わせたい場合は、 andThen メソッドを使用して条件を作成します。

Department_ d = new Department_();

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

次のように、クラス内に複数のスコープを定義できます。

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);
    }
}

ちょっとした便利機能

DAOでの実行(Entityql、NativeSql)

DAO インターフェースのデフォルトのメソッドで DSL を実行すると便利です。 config オブジェクトを取得するには、次のようにデフォルトのメソッドで Config.get(this) を呼び出します。

@Dao
public interface EmployeeDao {

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

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

テーブル名の上書き(Entityql、NativeSql)

メタモデル コンストラクターは修飾されたテーブル名を受け入れ、メタモデルはそのテーブル名を上書きします。

同じデータ構造を持つ 2 つのテーブルを処理すると便利です。

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

int count =
    nativeSql
        .insert(da)
        .select(c -> c.from(d))
        .execute();
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_

デバッグ (Entityql、NativeSql)

DSL によって構築された SQL ステートメントを知るには、 asSql メソッドを使用します。

Department_ d = new Department_();

Listable<Department> stmt = entityql.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());

上記のコードは次のように出力されます。

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'

asSql メソッドはデータベースに SQL ステートメントを発行しません。 SQL ステートメントを構築し、それを Sql オブジェクトとして返すだけです。

peek メソッドを呼び出して Sql オブジェクトを取得することもできます。

Department_ d = new Department_();

List<String> locations = nativeSql
        .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();

上記のコードは次のように出力されます。

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

サンプルプロジェクト