1.はじめに

このガイドでは、Java Persistence API(JPA)のうち、

SqlResultSetMapping

について説明します。

ここでの中心的な機能は、データベースのSQL文の結果セットをJavaオブジェクトにマッピングすることです。

2.セットアップ

使い方を見る前に、設定をしましょう。

2.1. メーベン依存

必要なMavenの依存関係はHibernateとH2 Databaseです。

Hibernate

はJPA仕様の実装を提供します。インメモリデータベースには、http://www.h2database.com/html/main.html[H2 Database]を使用します。

2.2. データベース

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

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


__EMPLOYEE


tableは、1つのresult

Entity

オブジェクトを格納します。


SCHEDULE

DAYS

__列

employeeIdによって

EMPLOYEE

表にリンクされたレコードが含まれます。

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

データ作成のためのスクリプトはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jpa[このガイドのコード]にあります。

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

私たちの

__Entity

__オブジェクトは似ているはずです。

@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

__classのアノテーションを使用します。

すべての

SqlResultSetMapping

アノテーションに必要なプロパティは

__nameだけです。


ただし、いずれかのメンバータイプがないと、何もマッピングされません。メンバー型は

ColumnResult



ConstructorResult

、および

EntityResult__です。

この場合、

__ ColumnResult

__は任意の列をスカラー結果型にマップします。

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


__ColumnResult


property

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

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


NamedNativeQuery

アノテーション内の

resultSetMapping

** の値は、

ResultSetMapping

宣言の

name

プロパティと一致するため、重要です。

その結果、

NamedNativeQuery

結果セットは期待どおりにマッピングされます。

同様に、

StoredProcedure

APIにはこの関連付けが必要です。

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<Long> employeeIds = em.createNamedQuery("FridayEmployees").getResultList();
    assertEquals(2, employeeIds.size());
}

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

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

4.1.

ConstructorResult


ColumnResult

の例と同様に、

Entity

クラスの

ScheduledDay



SqlResultMapping

アノテーションを追加します。

ただし、コンストラクターを使用してマップするには、コンストラクターを作成する必要があります。

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.baeldung.sqlresultsetmapping.ScheduledDay.class,
        columns={
          @ColumnResult(name="id", type=Long.class),
          @ColumnResult(name="employeeId", type=Long.class),
          @ColumnResult(name="dayOfWeek")})})


  • ColumnResults

    の順序は非常に重要です。** 列が正しくない場合、コンストラクタは識別されません。この例では、順序はテーブルの列と一致するため、実際には必要ありません。

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


__ConstructorResult


のもう1つのユニークな違いは、結果として得られるオブジェクトのインスタンス化が「new」または「detached」であることです。対応する主キーが

EntityManager

に存在する場合、マップされた

Entity__はデタッチ状態になります。それ以外の場合は新規になります。

SQLデータ型とJavaデータ型が一致しないために、実行時エラーが発生することがあります。したがって、

type.

で明示的に宣言できます。

4.2.

ConstructorResult

テスト

ユニットテストで

ConstructorResult

をテストしましょう。

@Test
public void whenNamedQuery__thenConstructorResult() {
  List<ScheduledDay> 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


propertyを使用します。

FieldResultと組み合わせると、__エイリアスと一致しないフィールドをマッピングできます。

@SqlResultSetMapping(
  name="EmployeeResult",
  entities={
    @EntityResult(
      entityClass = com.baeldung.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")


EntityResult

には、

ConstructorResult

と同様にコンストラクタが必要です。

しかし、デフォルトのものがここで動作します。

5.2. 複数のエンティティ

単一のEntityをマッピングしたら、複数のEntityをマッピングするのはかなり簡単です。

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

5.3.

EntityResult

テスト

実際の

EntityResult

を見てみましょう。

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

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

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

@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<Object[]> 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は、Java Persistence APIの重要な部分です。

コードスニペットはhttps://github.com/eugenp/tutorials/tree/master/persistence-modules/java-jpa[GitHubに追加]をご覧ください。