JPA 및 Hibernate로 복합 키를 맵핑하는 방법은 무엇입니까?


203

이 코드에서 복합 키에 대한 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;


답변:


415

복합 키를 매핑하려면, 당신은 사용할 수 있습니다 EmbeddedId 또는IdClass 주석을. 나는이 질문이 JPA에 관한 것이 아니라 사양에 의해 정의 된 규칙도 적용한다는 것을 알고 있습니다. 그래서 여기 있습니다 :

기본 키 및 엔터티 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어떤 식 으로든 키가 복합 키이고 , 결합 된 pk가 의미있는 엔티티 자체이거나 코드에서 재사용 될 때 IMO가 의미가 있음을보다 명확하게 전달 합니다 .
  • @IdClass 일부 필드 조합이 고유하지만 특별한 의미가 없음을 지정하는 데 유용합니다. .

또한 쿼리 작성 방식에 영향을 미칩니다 (약간의 장황한 표현).

  • IdClass

    select t.levelStation from Time t
  • EmbeddedId

    select t.timePK.levelStation from Time t

참고 문헌

  • JPA 1.0 사양
    • 섹션 2.1.4 "기본 키 및 엔티티 ID"
    • 9.1.14 절 "내장 된 주석 어노테이션"
    • 9.1.15 절. "IdClass 주석"

15
Hibernate 전용 솔루션도 있습니다. 외부 클래스를 식별자 유형으로 선언 하지 않고 여러 속성을 @Id 속성으로 매핑 하고 IdClass 주석을 사용하십시오. 5.1.2.1을 참조하십시오 . Hibernate 매뉴얼의 복합 식별자 .
Johan Boberg

이 질문을 좀 봐주시겠습니까? 멤버 필드 id가 항상 null생성되고 생성되지 않기 때문에 복합 기본 키에 문제가 있습니다 . /
displayname

두 경우 모두 어디에서 플레이하는지 알기가 어려우므로 getter 및 setter를 사용하여 예를 들어 주시겠습니까? 특히 IdClass 예제입니다. 감사. 아, 그리고 열 이름을 포함합니다. 감사합니다.
Jeremy

최대 절전 모드 특정 솔루션은 더 이상 사용되지 않습니다.
Nikhil Sahu

로부터 최대 절전 모드 주석 문서 에 대해 @IdClass"그것은 이전 버전과의 호환성을위한 EJB 2의 어두운 시대에서 상속 우리는 당신이 (단순 위해)를 사용하지 않는 것이 좋습니다되었습니다."
Marco Ferrari

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

@ 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갖는 엔티티 @ManyToOne에 연관 Employee두 통해 상위 클래스 합성 식별자를 참조해야 @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를 생성 할 수있는 도구가 있습니까?
레온

최대 절전 모드 도구를 사용해보십시오. 이를위한 리버스 엔지니어링 도구가 있습니다.
Vlad Mihalcea

7

기본 키 클래스는 equals 및 hashCode 메소드를 정의해야합니다.

  1. equals를 구현할 때 instanceof 를 사용하여 서브 클래스와 비교할 수 있도록 해야 합니다 . Hibernate lazy가 일대일 또는 다 대일 관계를로드하면 일반 클래스 대신 클래스에 대한 프록시가 있습니다. 프록시는 서브 클래스입니다. 클래스 이름을 비교할 수 없습니다.
    보다 기술적으로 : 당신은 Liskows 대체 원칙을 따르고 대칭성을 무시해야합니다.
  2. 다음 함정은 name.equals (that.getName ()) 대신 name.equals (that.name) 과 같은 것을 사용하는 것 입니다. 프록시 인 경우 첫 번째는 실패합니다.

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


6

처음부터이 작업을 수행 한 것 같습니다. 데이터베이스의 Netbeans Entities와 같은 사용 가능한 리버스 엔지니어링 도구를 사용하여 최소한 기본 사항 (예 : 임베디드 ID)을 자동화하십시오. 테이블이 많으면 큰 두통이 될 수 있습니다. 휠을 재발 명하지 말고 가능한 한 많은 도구를 사용하여 코딩을 최소 및 가장 중요한 부분으로 줄이십시오.


5

간단한 예를 들어 보자. 하자 두 테이블라는 말을 test하고 customer거기에 설명되어 있습니다 :

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 및를 추적하는 테이블이 하나 더 있습니다 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에서는 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;
  }
}

중요한 점은 우리는 또한 두 가지 기능을 구현한다는 것입니다 hashCode()equals()Hibernate가 그것들에 의존한다.


2

다른 옵션은 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
그것은 이해가되지 않습니다, 그는 기본 키 필요
마젠 Embaby

제목에 그는 기본이 될 필요가없는 복합 키를 말합니다
Enerccio

그는 SQL 기록 된 내용을 확인하시기 바랍니다 기본 키 (levelStation, confPathID)
마젠 Embaby을
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.