[Java/JPA] ネイティブクエリでデータ取得

SQLではできるけど、JPQLではどうしても実現できない条件があり、ネイティブクエリを使ってSQLを直接記述する方法を勉強したので、備忘録ということで。

まずはDBアクセスクラスにクエリを記述。

@Stateless
public class DbNativeQuery
       implements Serializable, IDbNativeQuery {

    private static final long serialVersionUID = 123456789L;

    @PersistenceContext(unitName = UnitName")
    private EntityManager em;

    public final List < DbList > outputList() {
        String sql = "SELECT id AS ID, name AS NAME, age AS AGE "
                   + "FROM table";
        List < DbList > list = null;
        try {
            Query query = em.createNativeQuery(sql, "DbList");
            query.setHint(TopLinkQueryHints.REFRESH, HintValues.TRUE);
            list = (List < DbList >) query.getResultList();
        } catch (Exception e) {
            System.out.println("Exception! [" + e.getMessage() + "]");
        }
        return list;
    }
}

続いて、エンティティクラス。

@SqlResultSetMapping(name = "DbList", entities = {
    @EntityResult(entityClass = DbList.class, fields = {
    @FieldResult(name = "ID",   column = "ID"),
    @FieldResult(name = "NAME", column = "NAME"),
    @FieldResult(name = "AGE",  column = "AGE")
    })
})
@Entity
public class DbList implements Serializable {
    private static final long serialVersionUID = 987654321L;

    @Id
    @Column(name = "ID")
    private String id;
    @Column(name = "NAME")
    private String name;
    @Column(name = "AGE")
    private String age;

    public DbList() {
    }

    public DbList(final String argId,
                  final String argName,
                  final String argAge) {
        this.id   = argId;
        this.name = argName;
        this.age  = argAge;
    }

    以下、必要に応じてgetterやsetterなど
}

上記のクエリならJPQLでも取れますが、まあごくごく簡単な条件でのネイティブクエリの使い方ということで。

ちなみに、マッピングしないのであればもっと簡単に書くことができ・・・ると思ったら、実はそんなに容易いものではなかった!

プライマリキーが変動したり、プライマリキーにしないといけないカラムにnullが入っている可能性があるため、マッピングする方法が使えない!・・・という事態にぶつかってしまったため、オブジェクト型で取る方法を試してみたところ、どうやらtoplinkの仕様ではオブジェクト型ではなく、Vector型に値をセットしてくるとのこと。

Vector型?なにそれ?

調べてみたら、Listを継承していて、ArrayListと兄弟のような型らしい。

だったら簡単じゃん、と思って、以下のようにしてみました。実際に同じコードを作ったわけではなく、以下はサンプルということで。さらに言うと、try/catchやクラスなどを省略しています。

Vector<Object[]> result = null;
Query q = this.em.createNativeQuery("SELECT a, b, c FROM table_name");
result = (Vector<Object[]>) q.getResultList();
for (Object[] obj : result) {
    this.valueA.add((String) obj[0]);
    this.valueB.add((Integer) obj[1]);
    this.valueC.add((String) obj[2]);
}

デバッグモードで見ていくと確かに値は取れているものの、for文でエラーが出る。Vector型からObject型にキャストはできません、と。

Vector.toArray()がObject[]型で値を返すので、Object[] obj = result.toArray()を試してみても、なぜかobjはVector型だからObjectにキャストできないと怒られる。

デバッグモードで値を見ると、確かにVector<E>と出ている。なんだこれ?

じゃあ、Vectorの中もVectorなら、これならどうだ!と試してみたのが以下のやり方。

Vector<Vector<Object[]>> result = null;
Query q = this.em.createNativeQuery("SELECT a, b, c FROM table_name");
result = (Vector<Vector<Object[]>>) q.getResultList();
for (Vector<Object[]> objVector : result) {
    Object[] obj = objVector.toArray();
    this.valueA.add((String) obj[0]);
    this.valueB.add((Integer) obj[1]);
    this.valueC.add((String) obj[2]);
}

ふつーに動いた。

これが合ってるのか間違ってるのかはわからないけど、結果が正しいから間違ってはいないのかな。

とりあえず動かすことができたので、備忘録としてメモメモ。

これ間違ってるよ!とか、わかってないなぁ…こうなんだよ!なんてご意見があれば、どしどし募集します!いまだにVectorが良くわからないので教えてください!(苦笑)

あ、ちなみに。

この方法でDBからデータを取得すると、文字列カラムはString、数値カラムはBigDecimalとして受け取ってあげればいいようです。

スポンサーリンク

この記事をシェア

アカウントをフォロー