JPAとHibernateで複合キーをマップする方法は?


204

このコードでは、複合キーの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;


1
例の本当に良いセット:vladmihalcea.com/2016/08/01/...
TecHunter

回答:


415

複合キーをマップするには、使用することができますEmbeddedId IdClass注釈を。この質問は厳密にJPAに関するものではないことを知っていますが、仕様で定義されているルールも適用されます。だからここにあります:

2.1.4主キーとエンティティID

...

複合主キーは、以下に説明するように、単一の永続フィールドまたはプロパティ、またはそのようなフィールドまたはプロパティのセットに対応している必要があります。主キークラスは、複合主キーを表すように定義する必要があります。複合主キーは、通常、データベースキーが複数の列で構成されているときに、レガシーデータベースからマッピングするときに発生します。そして 注釈は、複合主キーを示すために使用されます。セクション9.1.14および9.1.15を参照してください。EmbeddedIdIdClass

...

次のルールが複合主キーに適用されます。

  • 主キークラスはパブリックである必要があり、引数のないパブリックコンストラクターが必要です。
  • プロパティベースのアクセスを使用する場合、主キークラスのプロパティはパブリックまたは保護する必要があります。
  • 主キークラスはでなければなりませんserializable
  • 主キークラスは メソッドequalshashCodeメソッドを定義する必要があります。これらのメソッドの値の等価性のセマンティクスは、キーがマップされているデータベースタイプのデータベースの等価性と一致している必要があります。
  • 複合主キーは、埋め込み可能なクラスとして表現およびマッピングする(セクション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

参考文献

  • JPA 1.0仕様
    • 2.1.4項「主キーとエンティティのアイデンティティ」
    • セクション9.1.14「EmbeddedIdアノテーション」
    • セクション9.1.15「IdClassアノテーション」

15
Hibernate固有のソリューションもあります。外部クラスを識別子タイプとして宣言せずに、複数のプロパティを@Idプロパティとしてマップします(IdClassアノテーションを使用します)。5.1.2.1を参照してくださいHibernateマニュアルの複合識別子
Johan Boberg 2014年

この質問を見てください。メンバーフィールドidは常にnull生成されず、生成されないため、複合主キーで問題が発生します:/
displayname

どちらの場合でも、それらがどこに作用するのかわかりにくいので、ゲッターとセッターの例を挙げてください。特にIdClassの例。ありがとう。ああ、列名を含めてありがとう。
ジェレミー

ただし、休止状態固有のソリューションは非推奨です。
Nikhil Sahu

休止注釈ドキュメント、およそ@IdClass:「それは、後方互換性のためにEJB 2の暗黒の年代から継承されてきた、我々は(単純化のために)それを使用しないことをお勧めします」
マルコフェラーリ

49

使用する必要があります@EmbeddedId

@Entity
class Time {
    @EmbeddedId
    TimeId id;

    String src;
    String dst;
    Integer distance;
    Integer price;
}

@Embeddable
class TimeId implements Serializable {
    Integer levelStation;
    Integer confPathID;
}

@ Thierry-DimitriRoyでは、timeId.levelStationおよびtimeId.confPathIDをどのように割り当てることができますか。例を挙げていただけますか?
Duc Tran 2014年

@ Thierry-DimitriRoyプライマリクラスをエンティティクラスの静的内部クラスにできませんか?
Nikhil Sahu

はい、それは可能性があります
Samy Omar

17

この記事で説明したように、次のデータベーステーブルがあるとします。

ここに画像の説明を入力してください

最初に、@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ているエンティティは、2つのマッピングを介して親クラスから複合識別子を参照する必要があります。@ManyToOneEmployee@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;
    }
}

詳しくは、こちらの記事をご覧ください。


dbスキーマからEmployeeIdを生成できるツールはありますか?
レオン

Hibernateツールをお試しください。そのためのリバースエンジニアリングツールがあります。
Vlad Mihalcea

7

主キークラスは、equalsメソッドとhashCodeメソッドを定義する必要があります

  1. イコールを実装するときは、instanceofを使用してサブクラスと比較できるようにする必要があります。Hibernateが1対1または多対1の関係を遅延ロードする場合、プレーンクラスではなくクラスのプロキシが存在します。プロキシはサブクラスです。クラス名の比較は失敗します。
    より技術的に:Liskows置換原則に従い、対称性を無視する必要があります。
  2. 次の落とし穴はname.equals(that.name)のようなものを使用していますname.equals(that.getName())ではなく、name.equals(that.nameです。それがプロキシである場合、最初は失敗します。

http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html


6

ゼロからこれをやっているように見えます。Netbeans Entities from Databaseなどの利用可能なリバースエンジニアリングツールを使用して、少なくとも基本的な機能(埋め込みIDなど)を自動化してみてください。テーブルが多い場合、これは大きな頭痛の種になる可能性があります。ホイールを再発明することは避け、できるだけ多くのツールを使用して、コーディングを最小限に抑え、最も重要な部分、つまり意図したとおりに行うことをお勧めします。


5

簡単な例を見てみましょう。2つのテーブルが命名しましょうtestcustomerとしてそこに記載されています。

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));

testsとを追跡するテーブルがもう1つありますcustomer

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 entityClassでは、id_type_object主キーを使用してエンティティを検索およびロードするために、session.load(、)を使用します。複合キーの場合、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;
  }
}

重要な点は、2つの関数も実装してhashCode()おりequals()、Hibernateがそれらに依存していることです。


2

もう1つのオプションは、ConfPathテーブルの複合要素のマップとしてマップすることです。

ただし、このマッピングには(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>

1

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
        );
    }    
}

1
意味がありません。主キーが必要です
Mazen Embaby '15

タイトルで彼は複合キーと言います、それはプライマリである必要はありません
Enerccio '15

彼がSQLの主キー(levelStation、confPathID)を書き込んだことを確認してください
Mazen Embaby
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.