【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; } }
参考
2.3.2. ネイティブクエリのマッピング JBoss Enterprise Application Platform 5 | Red Hat Customer Portal