SqlResultSetMappingのガイド

SqlResultSetMappingへのガイド

1. 前書き

このガイドでは、Java Persistence API(JPA)のSqlResultSetMappingについて説明します。

ここでのコア機能には、データベースSQLステートメントからJavaオブジェクトへの結果セットのマッピングが含まれます。

2. セットアップ

その使用法を見る前に、いくつかの設定を行いましょう。

2.1. メーベン依存

必要なMaven依存関係は、HibernateおよびH2データベースです。 Hibernateは、JPA仕様の実装を提供します。 インメモリデータベースにはH2 Databaseを使用します。

2.2. データベース

次に、次のように2つのテーブルを作成します。

CREATE TABLE EMPLOYEE
(id BIGINT,
 name VARCHAR(10));

EMPLOYEE tableは、1つの結果Entityオブジェクトを格納します。 SCHEDULE_DAYS には、列employeeId:によってEMPLOYEEテーブルにリンクされたレコードが含まれます

CREATE TABLE SCHEDULE_DAYS
(id IDENTITY,
 employeeId BIGINT,
 dayOfWeek  VARCHAR(10));

データ作成用のスクリプトはthe code for this guideにあります。

2.3. エンティティオブジェクト

Entity objectsは次のようになります。

@Entity
public class Employee {
    @Id
    private Long id;
    private String name;
}

Entity objectsは、データベーステーブルとは異なる名前が付けられている可能性があります。 クラスに@Table のアノテーションを付けて、明示的にマッピングすることができます。

@Entity
@Table(name = "SCHEDULE_DAYS")
public class ScheduledDay {

    @Id
    @GeneratedValue
    private Long id;
    private Long employeeId;
    private String dayOfWeek;
}

3. スカラーマッピング

データができたので、クエリ結果のマッピングを開始できます。

3.1. ColumnResult

SqlResultSetMappingおよびQueryアノテーションはRepositoryクラスでも機能しますが、この例ではEntity クラスでアノテーションを使用します。

すべてのSqlResultSetMappingアノテーションには、name. という1つのプロパティのみが必要です。ただし、メンバータイプのいずれかがないと、何もマップされません。 メンバータイプは、ColumnResultConstructorResult、およびEntityResultです。

この場合、 ColumnResult は任意の列をスカラー結果タイプにマップします。

@SqlResultSetMapping(
  name="FridayEmployeeResult",
  columns={@ColumnResult(name="employeeId")})

ColumnResult propertynameは、クエリの列を識別します。

@NamedNativeQuery(
  name = "FridayEmployees",
  query = "SELECT employeeId FROM schedule_days WHERE dayOfWeek = 'FRIDAY'",
  resultSetMapping = "FridayEmployeeResult")

NamedNativeQueryアノテーションis important because it matches the name property from our ResultSetMapping declaration.the value of resultSetMappingに注意してください

その結果、NamedNativeQueryの結果セットは期待どおりにマッピングされます。 同様に、StoredProcedureAPIにはこの関連付けが必要です。

3.2. ColumnResultテスト

コードを実行するには、Hibernate固有のオブジェクトがいくつか必要です。

@BeforeAll
public static void setup() {
    emFactory = Persistence.createEntityManagerFactory("java-jpa-scheduled-day");
    em = emFactory.createEntityManager();
}

最後に、名前付きクエリを呼び出してテストを実行します。

@Test
public void whenNamedQuery_thenColumnResult() {
    List employeeIds = em.createNamedQuery("FridayEmployees").getResultList();
    assertEquals(2, employeeIds.size());
}

4. コンストラクターマッピング

結果セットをオブジェクト全体にマップする必要がある場合を見てみましょう。

4.1. ConstructorResult

ColumnResultの例と同様に、EntityクラスScheduledDaySqlResultMappingアノテーションを追加します。 ただし、コンストラクターを使用してマッピングするには、コンストラクターを作成する必要があります。

public ScheduledDay (
  Long id, Long employeeId,
  Integer hourIn, Integer hourOut,
  String dayofWeek) {
    this.id = id;
    this.employeeId = employeeId;
    this.dayOfWeek = dayofWeek;
}

また、マッピングはターゲットクラスと列を指定します(両方とも必須)。

@SqlResultSetMapping(
    name="ScheduleResult",
    classes={
      @ConstructorResult(
        targetClass=com.example.sqlresultsetmapping.ScheduledDay.class,
        columns={
          @ColumnResult(name="id", type=Long.class),
          @ColumnResult(name="employeeId", type=Long.class),
          @ColumnResult(name="dayOfWeek")})})

The order of the ColumnResults is very important.列の順序が正しくない場合、コンストラクターは識別されません。 この例では、順序はテーブルの列と一致するため、実際には必要ありません。

@NamedNativeQuery(name = "Schedules",
  query = "SELECT * FROM schedule_days WHERE employeeId = 8",
  resultSetMapping = "ScheduleResult")

ConstructorResult のもう1つの固有の違いは、結果として得られるオブジェクトのインスタンス化が「新規」または「切り離された」ことです。 一致する主キーがEntityManagerに存在する場合、マップされたEntityは切り離された状態になり、存在しない場合は新しい状態になります。

SQLデータ型とJavaデータ型の不一致が原因で、実行時エラーが発生する場合があります。 したがって、type.で明示的に宣言できます。

4.2. ConstructorResultテスト

単体テストでConstructorResultをテストしてみましょう。

@Test
public void whenNamedQuery_thenConstructorResult() {
  List scheduleDays
    = Collections.checkedList(
      em.createNamedQuery("Schedules", ScheduledDay.class).getResultList(), ScheduledDay.class);
    assertEquals(3, scheduleDays.size());
    assertTrue(scheduleDays.stream().allMatch(c -> c.getEmployeeId().longValue() == 3));
}

5. エンティティマッピング

最後に、コードが少ない単純なエンティティマッピングについて、EntityResultを見てみましょう。

5.1. 単一のエンティティ

EntityResultでは、エンティティクラスEmployeeを指定する必要があります。 より詳細に制御するために、オプションのfields プロパティを使用します。 FieldResult,と組み合わせると、一致しないエイリアスとフィールドをマッピングできます。

@SqlResultSetMapping(
  name="EmployeeResult",
  entities={
    @EntityResult(
      entityClass = com.example.sqlresultsetmapping.Employee.class,
        fields={
          @FieldResult(name="id",column="employeeNumber"),
          @FieldResult(name="name", column="name")})})

これで、クエリにエイリアス列が含まれるようになります。

@NamedNativeQuery(
  name="Employees",
  query="SELECT id as employeeNumber, name FROM EMPLOYEE",
  resultSetMapping = "EmployeeResult")

ConstructorResultと同様に、EntityResultにはコンストラクターが必要です。 ただし、ここではデフォルトのものが機能します。

5.2. 複数のエンティティ

単一のエンティティをマッピングしたら、複数のエンティティのマッピングは非常に簡単です。

@SqlResultSetMapping(
  name = "EmployeeScheduleResults",
  entities = {
    @EntityResult(entityClass = com.example.sqlresultsetmapping.Employee.class),
    @EntityResult(entityClass = com.example.sqlresultsetmapping.ScheduledDay.class)

5.3. EntityResultテスト

動作中のEntityResultを見てみましょう。

@Test
public void whenNamedQuery_thenSingleEntityResult() {
    List employees = Collections.checkedList(
      em.createNamedQuery("Employees").getResultList(), Employee.class);
    assertEquals(3, employees.size());
    assertTrue(employees.stream().allMatch(c -> c.getClass() == Employee.class));
}

複数のエンティティの結果が2つのエンティティを結合するため、1つのクラスのみのクエリアノテーションは混乱を招きます。

そのため、テストでクエリを定義します。

@Test
public void whenNamedQuery_thenMultipleEntityResult() {
    Query query = em.createNativeQuery(
      "SELECT e.id, e.name, d.id, d.employeeId, d.dayOfWeek "
        + " FROM employee e, schedule_days d "
        + " WHERE e.id = d.employeeId", "EmployeeScheduleResults");

    List results = query.getResultList();
    assertEquals(4, results.size());
    assertTrue(results.get(0).length == 2);

    Employee emp = (Employee) results.get(1)[0];
    ScheduledDay day = (ScheduledDay) results.get(1)[1];

    assertTrue(day.getEmployeeId() == emp.getId());
}

6. 結論

このガイドでは、SqlResultSetMappingアノテーション. SqlResultSetMapping isをJava PersistenceAPIの重要な部分として使用するためのさまざまなオプションについて説明しました。

コードスニペットはover on GitHubにあります。