Spring BootとJpaRepositoryを使ってEclipseのHibernate Toolsで生成したVIEW由来のEntityクラスの使い方です。
使用するデータベースはMySQLです。
テーブル由来のEntityクラスと違って注意すべき点がいくつかあるので、そのことについてまとめました。
環境
- OS:Windows10
- IDE:Eclipse2020-03
- Java:8
- MySQL:5.7
- Spring Boot:2.3.1
- Hibernate ORM:5.4
VIEW由来のEntityクラス
基本的な内容
Hibernate Toolsで生成したVIEW由来のEntityクラスを使うには、
- 主キーにあたるカラムに@Idを追記する
- クラス名の上に@Immutableを追記する
必要があります。
主キーにあたるカラムがshop_idであるshop_informationsビュー由来のEntityクラスであるShopInformation.javaの場合は以下の通り。
package jp.co.hibernate_exchange_with_mysql.domain.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import org.hibernate.annotations.Immutable;
/**
* The persistent class for the shop_informations database table.
*
*/
@Entity
@Immutable // 追記
@Table(name="shop_informations")
@NamedQuery(name="ShopInformation.findAll", query="SELECT s FROM ShopInformation s")
public class ShopInformation implements Serializable {
private static final long serialVersionUID = 1L;
private String address;
@Column(name="category_name")
private String categoryName;
@Id // 追記
@Column(name="shop_id", updatable = false, nullable = false) // 追記(任意)
private Integer shopId;
@Column(name="shop_name")
private String shopName;
private String tel;
@Column(name="zip_code")
private String zipCode;
public ShopInformation() {
}
public String getAddress() {
return this.address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCategoryName() {
return this.categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
public Integer getShopId() {
return this.shopId;
}
public void setShopId(Integer shopId) {
this.shopId = shopId;
}
public String getShopName() {
return this.shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public String getTel() {
return this.tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getZipCode() {
return this.zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
}
それぞれの項目について、解説していきます。
1. 主キーにあたるカラムに@Idを追記する
MySQLの場合、VIEWに主キーを定義できないため、Hibernate ORMでEntityクラスを生成した時点では@Idは付いていません(テーブルの場合は付与されている)。そのため、手動で追記してあげる必要があります。
この@Idを追記しないと、起動時に以下のようなエラーが出て落ちてしまいます。
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'shopInformationRepository' defined in jp.co.hibernate_exchange_with_mysql.domain.repository.ShopInformationRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Cannot resolve reference to bean 'jpaMappingContext' while setting bean property 'mappingContext'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity: jp.co.hibernate_exchange_with_mysql.domain.model.ShopInformation
・・・(以下省略)
2. クラス名の上に@Immutableを追記する
こちらは記載しなくても動きます。
しかし、VIEW上のデータは直接操作はできない(read only)ため、@Immutableをつけて変更不可とします。
なお、JpaRepositoryを継承したRepositoryインターフェースは以下の通り。
package jp.co.hibernate_exchange_with_mysql.domain.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import jp.co.hibernate_exchange_with_mysql.domain.model.ShopInformation;
@Repository
public interface ShopInformationRepository extends JpaRepository<ShopInformation, Integer>{
}
単一主キーのテーブルと同じく、extends JpaRepository<エンティティクラス, ID>のIDの部分には、主キーに当たるカラムのデータ型を書きます。
主キーにあたるカラムが複数存在する場合
VIEWに主キーにあたるカラムが複数存在する(複合主キーにあたる)場合は、上述の内容の他に、別途PKクラスを定義して、Entityクラス上で明示する必要があります。
例えば、主キーにあたるカラムがshop_id, item_id ,user_idのshop_itemsビュー由来のEntityクラスであるShopItem.javaの場合は以下の通り。
package jp.co.hibernate_exchange_with_mysql.domain.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import org.hibernate.annotations.Immutable;
/**
* The persistent class for the shop_items database table.
*
*/
@Entity
@Immutable // 追記
@IdClass(ShopItemPK.class) // 追記(別途PKクラスを定義)
@Table(name="shop_items")
@NamedQuery(name="ShopItem.findAll", query="SELECT s FROM ShopItem s")
public class ShopItem implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name="is_favorite")
private Integer isFavorite;
@Id // 追記
@Column(name="item_id", updatable = false , nullable = false) // 追記(任意)
private String itemId;
@Column(name="item_name")
private String itemName;
private Integer price;
@Id // 追記
@Column(name="shop_id", updatable = false , nullable = false) // 追記(任意)
private Integer shopId;
@Column(name="shop_name")
private String shopName;
@Id // 追記
@Column(name="user_id", updatable = false , nullable = false) // 追記(任意)
private Integer userId;
public ShopItem() {
}
public Integer getIsFavorite() {
return this.isFavorite;
}
public void setIsFavorite(Integer isFavorite) {
this.isFavorite = isFavorite;
}
public String getItemId() {
return this.itemId;
}
public void setItemId(String itemId) {
this.itemId = itemId;
}
public String getItemName() {
return this.itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public Integer getPrice() {
return this.price;
}
public void setPrice(Integer price) {
this.price = price;
}
public Integer getShopId() {
return this.shopId;
}
public void setShopId(Integer shopId) {
this.shopId = shopId;
}
public String getShopName() {
return this.shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
}
PK(プライマリーキー)クラスは以下の通りとなります。
package jp.co.hibernate_exchange_with_mysql.domain.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Embeddable;
/**
* The primary key class for the user_favorites database table.
*
*/
@Embeddable
public class ShopItemPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
@Column(name="shop_id")
private Integer shopId;
@Column(name="item_id")
private Integer itemId;
@Column(name="user_id")
private Integer userId;
public ShopItemPK() {
}
public Integer getShopId() {
return this.shopId;
}
public void setShopId(Integer shopId) {
this.shopId = shopId;
}
public Integer getItemId() {
return this.itemId;
}
public void setItemId(Integer itemId) {
this.itemId = itemId;
}
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof ShopItemPK)) {
return false;
}
ShopItemPK castOther = (ShopItemPK)other;
return
(this.shopId == castOther.shopId)
&& (this.itemId == castOther.itemId)
&& (this.userId == castOther.userId);
}
public int hashCode() {
final int prime = 31;
int hash = 17;
hash = hash * prime + this.shopId;
hash = hash * prime + this.itemId;
hash = hash * prime + this.userId;
return hash;
}
}
主キーが数値型以外の場合は、hashCode()メソッド内の記述を以下のようにします(.hashCode()を使います。)。
hash = hash * prime + this.itemId.hashCode(); //itemIdがString型の場合
Repositoryインターフェースは以下の通り。
package jp.co.hibernate_exchange_with_mysql.domain.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import jp.co.hibernate_exchange_with_mysql.domain.model.ShopItem;
import jp.co.hibernate_exchange_with_mysql.domain.model.ShopItemPK;
@Repository
public interface ShopItemRepository extends JpaRepository<ShopItem, ShopItemPK>{
}
extends JpaRepository<エンティティクラス, IDのIDの部分には、PKクラスを書きます(複合主キーを持つテーブルと同様)。
テーブル定義とVIEW定義
今回作成したEntityクラスのもととなるテーブルとVIEWの定義を以下に記載しておきます。
テーブル定義
create table shops (
shop_id INT not null AUTO_INCREMENT comment '店舗ID'
, shop_name VARCHAR(100) comment '店舗名'
, tel VARCHAR(15) comment '電話番号'
, zip_code VARCHAR(8) comment '郵便番号'
, address VARCHAR(150) comment '住所'
, category_id SMALLINT comment 'カテゴリID'
, created_by VARCHAR(100) comment '新規作成者'
, created_at DATETIME default now() comment '新規作成日'
, updated_by VARCHAR(100) comment '最終更新者'
, updated_at DATETIME default now() comment '最終更新日'
, is_deleted TINYINT default 0 comment '削除フラグ'
, constraint shop_PKC primary key (shop_id)
)ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin comment 'ショップ' ;
create table shop_categories (
category_id SMALLINT not null AUTO_INCREMENT comment 'カテゴリID'
, category_name VARCHAR(100) comment 'カテゴリ名'
, created_by VARCHAR(100) comment '新規作成者'
, created_at DATETIME default now() comment '新規作成日'
, updated_by VARCHAR(100) comment '最終更新者'
, updated_at DATETIME default now()comment '最終更新日'
, is_deleted TINYINT default 0 comment '削除フラグ'
, constraint shop_category_PKC primary key (category_id)
)ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin comment 'ショップカテゴリ' ;
create table items (
item_id VARCHAR(43) not null comment '商品ID'
, item_name VARCHAR(100) comment '商品名'
, price INT comment '価格'
, shop_id INT comment 'ショップID'
, created_by VARCHAR(100) comment '新規作成者'
, created_at DATETIME default now() comment '新規作成日'
, updated_by VARCHAR(100) comment '最終更新者'
, updated_at DATETIME default now() comment '最終更新日'
, is_deleted TINYINT default 0 comment '削除フラグ'
, constraint items_PKC primary key (item_id)
)ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin comment '商品' ;
create table users (
user_id INT not null AUTO_INCREMENT comment 'ユーザーID'
, user_name VARCHAR(100) comment 'ユーザー名'
, tel VARCHAR(15) comment '電話番号'
, zip_code VARCHAR(8) comment '郵便番号'
, address VARCHAR(150) comment '住所'
, password VARCHAR(20) comment 'パスワード'
, created_by VARCHAR(100) comment '新規作成者'
, created_at DATETIME default now() comment '新規作成日'
, updated_by VARCHAR(100) comment '最終更新者'
, updated_at DATETIME default now() comment '最終更新日'
, is_deleted TINYINT default 0 comment '削除フラグ'
, constraint users_PKC primary key (user_id)
)ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin comment 'ユーザー' ;
create table user_favorites (
item_id INT not null comment '商品ID'
, user_id INT not null comment 'ユーザーID'
, created_by VARCHAR(100) comment '新規作成者'
, created_at DATETIME default now() comment '新規作成日'
, updated_by VARCHAR(100) comment '最終更新者'
, updated_at DATETIME default now() comment '最終更新日'
, is_deleted TINYINT default 0 comment '削除フラグ'
, constraint user_favorites_PKC primary key (item_id,user_id)
)ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin comment 'お気に入り' ;
VIEW定義
create view `shop_informations`
as select
sh.shop_id,
sh.shop_name,
sc.category_name,
sh.zip_code,
sh.address,
sh.tel
from shops sh left outer join shop_categories sc on sh.category_id = sc.category_id and sc.is_deleted = 0
where sh.is_deleted = 0;
create view `shop_items`
as select
si.*,
case when exists (select * from user_favorites uf where uf.item_id = si.item_id and uf.user_id = si.user_id and is_deleted = 0) then 1 else 0 end as is_favorite
from
(select
sh.shop_id,
it.item_id,
us.user_id,
sh.shop_name,
it.item_name,
it.price
from shops sh inner join items it on sh.shop_id = it.shop_id
cross join users us
where sh.is_deleted = 0
and it.is_deleted = 0
and us.is_deleted = 0
order by shop_id, item_id, user_id) as si;