このコードでは、複合キー用のJavaクラスを生成する方法(休止状態で複合キーを作成する方法):
create table Time (
levelStation int(15) not null,
src varchar(100) not null,
dst varchar(100) not null,
distance int(15) not null,
price int(15) not null,
confPathID int(15) not null,
constraint ConfPath_fk foreign key(confPathID) references ConfPath(confPathID),
primary key (levelStation, confPathID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
複合キーをマッピングするには、EmbeddedId
またはIdClass
アノテーションを使用できます。この質問はJPAに関するものではありませんが、仕様で定義されている規則も適用されます。だからここに彼らは:
2.1.4主キーとエンティティID
...
複合主キーは、以下に説明するように、単一の永続フィールドまたはプロパティ、あるいはそのようなフィールドまたはプロパティのセットに対応している必要があります。主キークラスは、複合主キーを表すように定義する必要があります。データベースキーが複数の列で構成されているときに、レガシデータベースからマッピングすると、通常複合主キーが発生します。
EmbeddedId
およびIdClass
アノテーションは、複合主キーを示すために使用されます。 9.1.14節および9.1.15節を参照してください。...
次の規則が複合主キーに適用されます。
- 主キークラスはpublicでなければならず、publicの引数なしのコンストラクタを持っていなければなりません。
- プロパティベースのアクセスを使用する場合、主キークラスのプロパティはパブリックまたは保護されている必要があります。
- 主キークラスは
serializable
でなければなりません。- 主キークラスは
equals
メソッドとhashCode
メソッドを定義する必要があります。これらのメソッドの値の等価性のセマンティクスは、キーがマッピングされるデータベース型のデータベースの等価性と一致している必要があります。- 複合主キーは埋め込み可能なクラスとして表現されマッピングされるか(項9.1.14。「EmbeddedIdアノテーション」を参照)、表現されエンティティエンティティの複数のフィールドまたはプロパティにマッピングされる(項9.1.15。IdClassを参照)注釈」)。
- 複合主キークラスがエンティティクラスの複数のフィールドまたはプロパティにマップされている場合、主キークラスの主キーフィールドまたはプロパティの名前とエンティティクラスの名前は一致し、それらの型は同じである必要があります。
IdClass
を使って複合主キーのクラスは次のようになります(静的な内部クラスになる可能性があります)。
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, Integer confPathID) {
this.levelStation = levelStation;
this.confPathID = confPathID;
}
// equals, hashCode
}
そして実体:
@Entity
@IdClass(TimePK.class)
class Time implements Serializable {
@Id
private Integer levelStation;
@Id
private Integer confPathID;
private String src;
private String dst;
private Integer distance;
private Integer price;
// getters, setters
}
IdClass
アノテーションは、複数のフィールドをテーブルPKにマッピングします。
EmbeddedId
を使って複合主キーのクラスは次のようになります(静的な内部クラスになる可能性があります)。
@Embeddable
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, Integer confPathID) {
this.levelStation = levelStation;
this.confPathID = confPathID;
}
// equals, hashCode
}
そして実体:
@Entity
class Time implements Serializable {
@EmbeddedId
private TimePK timePK;
private String src;
private String dst;
private Integer distance;
private Integer price;
//...
}
@EmbeddedId
アノテーションは、PKクラスをテーブルPKにマップします。
@EmbeddedId
はどういうわけかキーが複合キーであり、IMOが意味をなすことをより明確に伝えます結合されたpkがそれ自体が意味のあるエンティティであるか、コードで再利用される場合。@IdClass
フィールドの組み合わせが一意であることを指定するのに役立ちますが、これらには特別な意味はありません。それらはあなたがクエリを書く方法にも影響を与えます(多かれ少なかれ冗長にします):
IdClass
で
select t.levelStation from Time t
EmbeddedId
で
select t.timePK.levelStation from Time t
あなたは @EmbeddedId
を使う必要があります:
@Entity
class Time {
@EmbeddedId
TimeId id;
String src;
String dst;
Integer distance;
Integer price;
}
@Embeddable
class TimeId implements Serializable {
Integer levelStation;
Integer confPathID;
}
この記事 で説明したように、次のデータベーステーブルがあるとします。
まず、複合識別子を保持する@Embeddable
を作成する必要があります。
@Embeddable
public class EmployeeId implements Serializable {
@Column(name = "company_id")
private Long companyId;
@Column(name = "employee_number")
private Long employeeNumber;
public EmployeeId() {
}
public EmployeeId(Long companyId, Long employeeId) {
this.companyId = companyId;
this.employeeNumber = employeeId;
}
public Long getCompanyId() {
return companyId;
}
public Long getEmployeeNumber() {
return employeeNumber;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmployeeId)) return false;
EmployeeId that = (EmployeeId) o;
return Objects.equals(getCompanyId(), that.getCompanyId()) &&
Objects.equals(getEmployeeNumber(), that.getEmployeeNumber());
}
@Override
public int hashCode() {
return Objects.hash(getCompanyId(), getEmployeeNumber());
}
}
これで、複合識別子を使用するEmployee
エンティティに@EmbeddedId
というアノテーションを付けることでマッピングできます。
@Entity(name = "Employee")
@Table(name = "employee")
public class Employee {
@EmbeddedId
private EmployeeId id;
private String name;
public EmployeeId getId() {
return id;
}
public void setId(EmployeeId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Phone
への@ManyToOne
関連を持つEmployee
エンティティは、2つの@JoinColumn
マッピングを介して親クラスからの複合識別子を参照する必要があります。
@Entity(name = "Phone")
@Table(name = "phone")
public class Phone {
@Id
@Column(name = "`number`")
private String number;
@ManyToOne
@JoinColumns({
@JoinColumn(
name = "company_id",
referencedColumnName = "company_id"),
@JoinColumn(
name = "employee_number",
referencedColumnName = "employee_number")
})
private Employee employee;
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
詳しくは この記事 をご覧ください。
あなたが一からこれをやっているように見えます。少なくとも基本を自動化するために(組み込みIDのように)、DatabaseのNetbeans Entitiesのような利用可能なリバースエンジニアリングツールを試してみてください。テーブルがたくさんある場合、これは大きな頭痛の種になる可能性があります。私は、輪を作り直すことを避け、可能な限り多くのツールを使用して、コーディングを最小限かつ最も重要な部分、つまり意図するものに減らすことをお勧めします。
主キークラスはequalsとhashCodeメソッドを定義しなければならない
簡単な例を見てみましょう。 test
とcustomer
という2つのテーブルがあるとしましょう。
create table test(
test_id int(11) not null auto_increment,
primary key(test_id));
create table customer(
customer_id int(11) not null auto_increment,
name varchar(50) not null,
primary key(customer_id));
test
sとcustomer
を追跡するもう1つのテーブルがあります。
create table tests_purchased(
customer_id int(11) not null,
test_id int(11) not null,
created_date datetime not null,
primary key(customer_id, test_id));
テーブルtests_purchased
では主キーが複合キーであることがわかりますので、<composite-id ...>...</composite-id>
マッピングファイルでhbm.xml
タグを使用します。 PurchasedTest.hbm.xml
は次のようになります。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="entities.PurchasedTest" table="tests_purchased">
<composite-id name="purchasedTestId">
<key-property name="testId" column="TEST_ID" />
<key-property name="customerId" column="CUSTOMER_ID" />
</composite-id>
<property name="purchaseDate" type="timestamp">
<column name="created_date" />
</property>
</class>
</hibernate-mapping>
しかし、それはここで終わりません。 Hibernateでは、session.load(entityClass
、id_type_object
)を使用して、主キーを使用してエンティティを見つけてロードします。複合キーの場合、IDオブジェクトは別のIDクラス(上記の場合はPurchasedTestId
クラス)にする必要があります。これは、次のように主キー属性を宣言するだけです :
import Java.io.Serializable;
public class PurchasedTestId implements Serializable {
private Long testId;
private Long customerId;
// an easy initializing constructor
public PurchasedTestId(Long testId, Long customerId) {
this.testId = testId;
this.customerId = customerId;
}
public Long getTestId() {
return testId;
}
public void setTestId(Long testId) {
this.testId = testId;
}
public Long getCustomerId() {
return customerId;
}
public void setCustomerId(Long customerId) {
this.customerId = customerId;
}
@Override
public boolean equals(Object arg0) {
if(arg0 == null) return false;
if(!(arg0 instanceof PurchasedTestId)) return false;
PurchasedTestId arg1 = (PurchasedTestId) arg0;
return (this.testId.longValue() == arg1.getTestId().longValue()) &&
(this.customerId.longValue() == arg1.getCustomerId().longValue());
}
@Override
public int hashCode() {
int hsCode;
hsCode = testId.hashCode();
hsCode = 19 * hsCode+ customerId.hashCode();
return hsCode;
}
}
重要な点は、Hibernateがそれらに依存しているので、2つの関数hashCode()
とequals()
も実装していることです。
もう1つのオプションは、ConfPathテーブル内の複合要素のMapとしてマップすることです。
ただし、このマッピングは(ConfPathID、levelStation)のインデックスの恩恵を受けるでしょう。
public class ConfPath {
private Map<Long,Time> timeForLevelStation = new HashMap<Long,Time>();
public Time getTime(long levelStation) {
return timeForLevelStation.get(levelStation);
}
public void putTime(long levelStation, Time newValue) {
timeForLevelStation.put(levelStation, newValue);
}
}
public class Time {
String src;
String dst;
long distance;
long price;
public long getDistance() {
return distance;
}
public void setDistance(long distance) {
this.distance = distance;
}
public String getDst() {
return dst;
}
public void setDst(String dst) {
this.dst = dst;
}
public long getPrice() {
return price;
}
public void setPrice(long price) {
this.price = price;
}
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
}
マッピング:
<class name="ConfPath" table="ConfPath">
<id column="ID" name="id">
<generator class="native"/>
</id>
<map cascade="all-delete-Orphan" name="values" table="example"
lazy="extra">
<key column="ConfPathID"/>
<map-key type="long" column="levelStation"/>
<composite-element class="Time">
<property name="src" column="src" type="string" length="100"/>
<property name="dst" column="dst" type="string" length="100"/>
<property name="distance" column="distance"/>
<property name="price" column="price"/>
</composite-element>
</map>
</class>
Hbm.xmlを使用する
<composite-id>
<!--<key-many-to-one name="productId" class="databaselayer.users.UserDB" column="user_name"/>-->
<key-property name="productId" column="PRODUCT_Product_ID" type="int"/>
<key-property name="categoryId" column="categories_id" type="int" />
</composite-id>
注釈を使う
複合キークラス
public class PK implements Serializable{
private int PRODUCT_Product_ID ;
private int categories_id ;
public PK(int productId, int categoryId) {
this.PRODUCT_Product_ID = productId;
this.categories_id = categoryId;
}
public int getPRODUCT_Product_ID() {
return PRODUCT_Product_ID;
}
public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
this.PRODUCT_Product_ID = PRODUCT_Product_ID;
}
public int getCategories_id() {
return categories_id;
}
public void setCategories_id(int categories_id) {
this.categories_id = categories_id;
}
private PK() { }
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
PK pk = (PK) o;
return Objects.equals(PRODUCT_Product_ID, pk.PRODUCT_Product_ID ) &&
Objects.equals(categories_id, pk.categories_id );
}
@Override
public int hashCode() {
return Objects.hash(PRODUCT_Product_ID, categories_id );
}
}
エンティティクラス
@Entity(name = "product_category")
@IdClass( PK.class )
public class ProductCategory implements Serializable {
@Id
private int PRODUCT_Product_ID ;
@Id
private int categories_id ;
public ProductCategory(int productId, int categoryId) {
this.PRODUCT_Product_ID = productId ;
this.categories_id = categoryId;
}
public ProductCategory() { }
public int getPRODUCT_Product_ID() {
return PRODUCT_Product_ID;
}
public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
this.PRODUCT_Product_ID = PRODUCT_Product_ID;
}
public int getCategories_id() {
return categories_id;
}
public void setCategories_id(int categories_id) {
this.categories_id = categories_id;
}
public void setId(PK id) {
this.PRODUCT_Product_ID = id.getPRODUCT_Product_ID();
this.categories_id = id.getCategories_id();
}
public PK getId() {
return new PK(
PRODUCT_Product_ID,
categories_id
);
}
}