ねぎとろ放浪記

ねぎとろ放浪記

個人的備忘録です。勉強したことをまとめていきます。

【Spring Data JPA】テーブル結合を含むクエリの結果を受け取る

INNER JOIN などのテーブル結合を含むクエリの結果の受け取り方で詰まったのでメモ。

@SqlRusltSetMappingを使用するか、Objectの配列を目的の型に変換する方法がある。

例として、ItemsテーブルとPricesテーブルを結合し、ItemごとのPriceの結果を受け取る。

テーブルの定義

CREATE TABLE items(
id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(16) NOT NULL
);

CREATE TABLE prices(
id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT,
item_id BIGINT,
price BIGINT NOT NULL
);


方法1 - SqlResultMappingを使う

@SqlRusltSetMappingを使用することで受け取れる。

ただしこの方法は、テーブルに対応しないEntityを作成するので、混乱を招く可能性がある。

結果を受け取るためのクラス(Entity)

@Entity
@SqlResultSetMapping(
        name="ItemPricesResult",
        classes = {
                @ConstructorResult(
                        targetClass = ItemPrices.class,
                        columns = {
                                @ColumnResult(name="id", type=Long.class),
                                @ColumnResult(name="name", type=String.class),
                                @ColumnResult(name="price", type=Long.class)
                        }
                )
        }
)
@NamedNativeQuery(
        name="ItemPricesResult",
        query = "SELECT items.id, items.name, prices.price " +
                "FROM items " +
                "INNER JOIN prices ON (items.id = prices.item_id)",
        resultSetMapping = "ItemPricesResult"
)
public class ItemPrices {

    public ItemPrices(Long id, String name, Long price){
        this.id = id;
        this.name = name;
        this.price = price;
    }

    @Id
    private Long id;

    private String name;

    private Long price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getPrice() {
        return price;
    }

    public void setPrice(Long price) {
        this.price = price;
    }
}

@Idは必須。 コンストラクタを定義する必要がある。

Service

@Service
public class ItemService {

    @PersistenceContext
    EntityManager entityManager;

    public List<ItemPrices> getItemPrices(){
        return entityManager.createNamedQuery("ItemPricesResult").getResultList();
    }
}

repositoryを介さずに呼び出せる。

方法2 - Objectの配列として受け取り変換

新しいDtoを作成する。

@Data
@AllArgsConstructor
public class ItemPriceDto{

    private Integer itemId;
    private String name;
    private Integer price;

    public ItemPriceDto(Object[] objects) {
        this(
                ((BigInteger) objects[0]).intValue(),
                (String) objects[1],
                ((BigInteger) objects[2]).intValue()
                );
    }
}

Repository

public interface ItemRepository extends JpaRepository<Item, Integer> {

    @Query(
            value ="SELECT items.id as item_id, items.name, prices.price " +
                    "FROM items " +
                    "INNER JOIN prices ON (items.id = prices.item_id)",
            nativeQuery = true
    )
    List<Object[]> findItemPricesRaw();

    default List<ItemPriceDto> findItemPrices(){
        return findItemPricesRaw()
                .stream()
                .map(ItemPriceDto::new)
                .collect(Collectors.toList());
    }
}

findItemPriceRawはObjectの配列を返す。

これをItemPriceDtoのリストに変換する。


戻り値が1つのときは、objectの0番目の値を指定してコンストラクタに渡す。

public interface ItemRepository extends JpaRepository<Item, Integer> {

    @Query(
            value ="SELECT items.id as item_id, items.name, prices.price " +
                    "FROM items " +
                    "INNER JOIN prices ON (items.id = prices.item_id) " +
                    "WHERE items.id = :itemId",
            nativeQuery = true
    )
    Object[] findSingleItemPriceRaw(Integer itemId);

    default ItemPriceDto findSingleItemPrice(Integer itemId){
        Object[] objects = findSingleItemPriceRaw(itemId);
        ItemPriceDto itemPriceDto = new ItemPriceDto((Object[]) objects[0]); // objectsの0番目の値を指定
        return itemPriceDto;
    }
}

参考

6.3. データベースアクセス(JPA編) — TERASOLUNA Server Framework for Java (5.x) Development Guideline 5.3.1.RELEASE documentation

2.3.2. ネイティブクエリのマッピング JBoss Enterprise Application Platform 5 | Red Hat Customer Portal