ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 JPA 和 Hibernate 映射复合键?

在这段代码中,如何为组合键生成一个Java类(如何在hibernate中组合键):

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;

a
approxiblue

要映射复合键,您可以使用 EmbeddedId IdClass 注释。我知道这个问题并不严格与 JPA 有关,但规范定义的规则也适用。所以他们在这里:

2.1.4 主键和实体标识 ... 复合主键必须对应于单个持久字段或属性或一组这样的字段或属性,如下所述。必须定义一个主键类来表示一个复合主键。当数据库键由多个列组成时,通常会在从遗留数据库映射时出现复合主键。 EmbeddedId 和 IdClass 注释用于表示复合主键。请参阅第 9.1.14 和 9.1.15 节。 ...以下规则适用于复合主键:主键类必须是公共的,并且必须具有公共的无参数构造函数。如果使用基于属性的访问,则主键类的属性必须是公共的或受保护的。主键类必须是可序列化的。主键类必须定义 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 以某种方式更清楚地传达了密钥是复合密钥,并且当组合的 pk 本身是一个有意义的实体或者它在您的代码中重用时,IMO 才有意义。

@IdClass 可用于指定某些字段组合是唯一的,但这些组合没有特殊含义。

它们还会影响您编写查询的方式(使它们或多或少冗长):

使用 IdClass 从时间 t 中选择 t.levelStation

使用 EmbeddedId 从时间 t 中选择 t.timePK.levelStation

参考

JPA 1.0 规范 Section 2.1.4 “Primary Keys and Entity Identity” Section 9.1.14 “EmbeddedId Annotation” Section 9.1.15 “IdClass Annotation”

第 2.1.4 节“主键和实体身份”

第 9.1.14 节“EmbeddedId 注释”

第 9.1.15 节“IdClass 注释”


还有一个特定于 Hibernate 的解决方案:将多个属性映射为 @Id 属性而不将外部类声明为标识符类型(并使用 IdClass 注释)。请参阅 Hibernate 手册中的 5.1.2.1. Composite identifier
请看一下this question好吗?我在使用复合主键时遇到问题,因为成员字段 id 始终为 null 并且不会生成:/
请举一个 getter 和 setter 的例子,因为在这两种情况下我都很难看到它们在哪里发挥作用。尤其是 IdClass 示例。谢谢。哦,包括列名,谢谢。
尽管不推荐使用休眠特定的解决方案。
来自 Hibernate Annotations docs,关于 @IdClass:“它继承自 EJB 2 的黑暗时代以实现向后兼容性,我们建议您不要使用它(为简单起见)。”
A
Abdullah Khan

您需要使用 @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。你能提供一个例子吗?
@Thierry-DimitriRoy 主类不能是实体类的静态内部类吗?
是的,它可能是
V
Vlad Mihalcea

假设您有以下数据库表:

https://i.stack.imgur.com/80ZNJ.png

首先,您需要创建包含复合标识符的 @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());
    }
}

有了这个,我们可以通过使用 @EmbeddedId 注释来映射使用复合标识符的 Employee 实体:

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

Employee 具有 @ManyToOne 关联的 Phone 实体需要通过两个 @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;
    }
}

是否有可以从数据库模式生成 EmployeeId 的工具?
试试休眠工具。它有一个逆向工程工具。
M
Mike

主键类必须定义equals和hashCode方法

在实现 equals 时,您应该使用 instanceof 来允许与子类进行比较。如果 Hibernate 延迟加载一对一或多对一关系,您将拥有该类的代理而不是普通类。代理是一个子类。比较类名会失败。从技术上讲:您应该遵循 Liskows 替代原则并忽略对称性。下一个陷阱是使用类似 name.equals(that.name) 而不是 name.equals(that.getName()) 的东西。如果那是代理,第一个将失败。

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


j
javydreamercsw

看起来你是从头开始做的。尝试使用可用的逆向工程工具,例如来自数据库的 Netbeans 实体,至少使基础自动化(例如嵌入式 ID)。如果您有很多桌子,这可能会变得非常头疼。我建议避免重新发明轮子,并使用尽可能多的工具将编码减少到最小和最重要的部分,即您打算做什么。


K
Kenny Dewhirst

让我们举一个简单的例子。假设有两个名为 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));

还有一张表格,用于跟踪 testcustomer

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 中,主键是一个复合键,因此我们将在 hbm.xml 映射文件中使用 <composite-id ...>...</composite-id> 标记。所以 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 依赖它们。


C
Camilo Sanchez

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

这没有意义,他需要主键
在标题中,他说复合键,不一定是主键
请检查他写的sql主键(levelStation,confPathID)
M
Maurice Perry

另一种选择是将映射作为 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>