😀

(spring-data-jpa)JPA2.1を利用してPOJO(エンティティクラス以外)を取得する方法

2016/12/30に公開

はじめに

通常spring-data-jpaを使用して値を取得する場合JPQLを利用するにせよNativeQuqeryを使用するにせよ、
場合エンティティ(テーブルクラス)を戻り値として受け取りますが、例えばテーブルを結合した際の戻り値等
エンティティ以外のクラスで値を受け取る場合の方法について記載します。

今回は下記のようにパーティション一覧の取得を実施してみましょう。

query
SELECT partition_name, table_rows  FROM information_schema.partitions where table_name = 'user';

#Version

今回は下記バージョンで実施します。
spring-data-jpa: 1.10.5.RELEASE
hibernate-entitymanager: 5.1.0.Final

- maven定義

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.10.5RELEASE</version>
        </dependency>
        <!-- JPA -provider(Hibernate) -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.0.Final</version>
        </dependency>

- Versionに関する注意点

上記フレームワーク内にはJPAが内包されていますが、必ず上記バージョン以上で利用してください。
今回のエンティティクラス以外で受け取る方法はJPA2.1から利用できます。
例えばhibernate-entitymanage4系ではJPA2.0で利用できないため注意が必要です。
また上記spring-data-jpaもhibernate5系に合わせたバージョンで使用する必要があります。

この辺のバージョン管理の見通しの悪さと面倒臭さが難点ですね。。

#@SqlResultSetMappingを利用して取得

- 対象POJO

SqlResultSetMappingを使用してuserテーブルのパーティション名とレコード数を取得してみます。

実行query
SELECT partition_name, table_rows  FROM information_schema.partitions where table_name = 'user';
Partition.class(戻り値格納用クラス)

@AllArgsConstructor
@Data
public class Partition implements Serializable {
    private static final long serialVersionUID = -9071293948637991593L;
    /** パーティション名 */
    private String partitionName;
    /** レコード数 */
    private String table_rows;
}

- @SqlResultSetMapping定義

Jpa2.1からSqlResultSetMappingにおいてConstructorResultが利用できるようになりました。
上記Partition.classで受け取るには下記のように定義します。
nameについては任意で自由に定義でき、@ConstructorResultでは受け取るクラスとカラム定義を指定します。

この@SqlResultSetMappingは必ずエンティティクラス上に実装してください。
エンティティクラス外に定義するとMappingExceptionが発生します。

SqlResultSetMapping定義方法
@SqlResultSetMapping(
  name = "User.Partition", 
  classes = { 
    @ConstructorResult(
      targetClass = com.sample.orm.entity.transience.Partition.class,
      columns = { 
        @ColumnResult(name = "partition_name", type = String.class)
        @ColumnResult(name = "table_rows", type = String.class)
      }
    )
  }
)
@Data
@Entity
@Table(name = "user")
public class User implements Serializable {

   ︙
   ︙
}

- SQL実行メソッド

NativeQueryで取得する際には、上記mapping名を指定すれば取得できます。
もちろんJQPLでも利用可能です。

List<Partition> results = em
          .createNativeQuery(
            "SELECT partition_name, table_rows FROM information_schema.partitions where table_name = 'user'",
            "User.Partition" ) 
          .getResultList()

orm.xmlのsql-result-set-mappingを使用する。

- orm.xml配置箇所

前述の@SqlResultSetMappingを利用した方法は必ずエンティティクラス上に定義する必要があるため、複数定義する場合どうしてもエンティティが煩雑になりがちです。(各POJOには定義するという方法は使えないため)
それを避けるためにorm.xmlを利用しましょう。
下記「パッケージ構成」をのようにMETA-INF直下にorm.xmlに配置すればapplicationContext.xmlで定義しなくても読み込まれます。

パッケージ構成
ーjava
ーresources
     |
     |_conf
              |_☓☓☓
              |_☓☓☓
     |_META-INF
              |_orm.xml
     |_applicationContext.xml

- orm.xml定義

下記がorm.xmlの定義です。
「entity-mappings」の定義はversion2.1とそれ以外で異なりますので注意です。
「constructor-result」でPOJOを定義するのは2.1からの機能のため必ず2.1を指定してください。

「sql-result-set-mapping」のnameは任意ですが、「named-native-query」の「result-set-mapping」と一致させてください。
また「named-native-query」については必須ではありません。実装クラスで直接SQLを記載しても動作します。

orm.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings
        xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm_2_1.xsd"
        version="2.1">

    <named-native-query name="User.findPartition" result-set-mapping="User.Partition">
        <query><![CDATA[SELECT partition_name, table_rows FROM information_schema.partitions where table_name = 'user']]></query>
    </named-native-query>

    <sql-result-set-mapping name="User.Partition">
        <constructor-result target-class="com.sample.orm.entity.transience.Partition">
            <column name="partitionName" class="java.lang.String"/>
            <column name="tableRows" class="java.lang.Integer"/>
        </constructor-result>
    </sql-result-set-mapping>
</entity-mappings>

- SQL実行

下記のように「named-native-query」のnameを指定することで取得できます。

List<Partition> results = em
          .createNamedQuery("User.findPartition") 
          .getResultList()

Discussion