アグリゲート戦略

アグリゲート戦略は、任意のSELECT文からエンティティアグリゲートを構築する方法を定義します。リレーショナルなクエリ結果を階層構造のエンティティにマッピングするために、エンティティ同士の関連付け方法を指定する構造化されたアプローチを提供します。

アグリゲート戦略の定義

アグリゲート戦略は、インターフェースに @AggregateStrategy アノテーションを付与することで定義されます。このアノテーションは、クエリ結果からエンティティのアグリゲートをどのように再構築するかを指定します。

@AggregateStrategy(root = Department.class, tableAlias = "d")
interface DepartmentAggregateStrategy {
    // ...
}
  • root 要素は、アグリゲートのルートとして機能するエンティティクラスを指定します。

  • tableAlias 要素は、ルートのエンティティクラスに対応するテーブルのエイリアスを指定します。このエイリアスは、クエリの結果をエンティティのプロパティに正しくマップするためにSELECT文で利用される必要があります。

関連付けリンカーの定義

アグリゲート戦略には、 @AssociationLinker で注釈された BiConsumer または BiFunction 型のフィールドが少なくとも1つ含まれている必要があります。これらの関数は、2つのエンティティインスタンスを動的に関連付ける責任があります。不変エンティティを関連付ける場合は、BiFunction を使用します。変更可能なエンティティの場合は、BiConsumer または BiFunction のいずれかを使用できます。

@AggregateStrategy(root = Department.class, tableAlias = "d")
interface DepartmentAggregateStrategy {
  @AssociationLinker(propertyPath = "employees", tableAlias = "e")
  BiConsumer<Department, Employee> employees =
      (d, e) -> {
        d.getEmployees().add(e);
        e.setDepartment(d);
      };

  @AssociationLinker(propertyPath = "employees.address", tableAlias = "a")
  BiFunction<Employee, Address, Employee> address =
      (e, a) -> {
        e.setAddress(a);
        return e;
      };
}
  • BiConsumerBiFunction の1番目の型パラメータはプロパティオーナーの型を表し、2番目の型パラメータはプロパティの型を表します。 BiFunction の3番目の型パラメータは1番目のものと同じでなければならず、関連付けが適用された後のエンティティの型を表します。

  • propertyPath 要素は、ルートエンティティクラスからドットで区切られたパスとしてターゲットプロパティの名前を指定します。

  • tableAlias 要素は、BiFunction の 2 番目の型パラメータとして使用されるエンティティクラスに対応するテーブルのエイリアスを指定します。 このエイリアスはSELECT文で使用する必要があります。

上記の DepartmentAggregateStrategy は以下のエンティティ定義に基づいています。

@Entity(naming = NamingType.SNAKE_LOWER_CASE)
public class Department {
  @Id Integer id;
  String name;
  @Association List<Employee> employees = new ArrayList<>();

  // getter, setter
}

@Entity(naming = NamingType.SNAKE_LOWER_CASE)
public class Employee {
  @Id Integer id;
  String name;
  Integer departmentId;
  Integer addressId;
  @Association Department department;
  @Association Address address;

  // getter, setter
}

@Entity(naming = NamingType.SNAKE_LOWER_CASE)
public class Address {
  @Id Integer id;
  String street;

  // getter, setter
}

エンティティクラスでは、関連プロパティに @Association を付与する必要があります。これらのプロパティは @AssociationLinker を使用してリンクできます。

アグリゲート戦略の使用

DepartmentAggregateStrategy@SelectaggregateStrategy 要素に指定することで使用されます。

@Dao
interface DepartmentDao {
  @Select(aggregateStrategy = DepartmentAggregateStrategy.class)
  Department selectById(Integer id);
}

selectById メソッドは、次のような SELECT ステートメントを必要とします:

select
    d.id as d_id,
    d.name as d_name,
    a.id as a_id,
    a.street as a_street,
    e.id as e_id,
    e.name as e_name,
    e.department_id as e_department_id,
    e.address_id as e_address_id
from
    department d
    left outer join
    employee e on (d.id = e.department_id)
    left outer join
    address a on (e.address_id = a.id)
where
    d.id = /* id */0

注釈

SELECTリストにはアグリゲートを形成するすべてのエンティティのIDを含める必要があります。

カラムエイリアスのルール

  • テーブルエイリアスは DepartmentAggregateStrategy で定義されているものと一致する必要があります。

  • カラムのエイリアスは、テーブルのエイリアスにアンダースコア(_)を付けた形式で指定する必要があります。例えば、d.idd_ide.ide_id としてエイリアスされます。

選択カラムリスト展開ディレクティブの使用

選択カラムリスト展開ディレクティブ を使用することで、上記のSELECT文をより簡潔に記述することができます。

select
    /*%expand*/*
from
    department d
    left outer join
    employee e on (d.id = e.department_id)
    left outer join
    address a on (e.address_id = a.id)
where
    d.id = /* id */0

カラムリスト展開の仕組み

  • /*%expand */* ディレクティブは、あらかじめ定義されたエイリアスルールを使用して列リストに自動的に展開します。

  • デフォルトでは、すべてのテーブルのすべての列が結果セットに含まれます。

特定のテーブルのみを選択的に展開するには、テーブルエイリアスのカンマ区切りリストを渡します。

select
    /*%expand "e, d" */*,
    a.id as a_id,
    a.street as a_street
from
    department d
    left outer join
    employee e on (d.id = e.department_id)
    left outer join
    address a on (e.address_id = a.id)
where
    d.id = /* id */0
  • ここでは、テーブルの e (employee) と d (department) のカラムのみが展開されます。

  • テーブル a (address) の列は明示的に指定されています。