Dans ce code, comment générer une classe Java pour la clé composite (comment composer une clé en veille prolongée):
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;
Pour mapper une clé composite, vous pouvez utiliser les annotations EmbeddedId
ou _ IdClass
. Je sais que cette question ne concerne pas strictement JPA, mais les règles définies par la spécification s’appliquent également. Alors les voici:
2.1.4 Clés primaires et identité de l'entité
...
Une clé primaire composite doit correspondent à un seul champ persistant ou propriété ou à un ensemble de champs ou propriétés tels que décrit ci-dessous. Une classe de clé primaire doit être défini pour représenter un clé primaire composite. Composite les clés primaires apparaissent généralement lorsque mappage à partir de bases de données existantes lorsque le fichier la clé de base de données est composée de plusieurs colonnes. Les annotations
EmbeddedId
etIdClass
sont utilisées pour désigner Des clés primaires composites. Voir sections 9.1.14 et 9.1.15....
Les règles suivantes s'appliquent pour clés primaires composites:
- La classe de clé primaire doit être publique et doit avoir un constructeur public no-arg }
- Si un accès basé sur une propriété est utilisé, les propriétés de la clé primaire la classe doit être publique ou protégée.
- La classe de clé primaire doit être
serializable
.- La classe de clé primaire Doit définir les méthodes
equals
ethashCode
} La sémantique de la valeur l'égalité pour ces méthodes doit être compatible avec l'égalité de la base de données pour les types de base de données auxquels le la clé est mappée.- Une clé primaire composite doit être représentée et mappée en tant que classe incorporable (voir Section 9.1.14, “Annotation EmbeddedId”) ou doit être représenté et mappé à plusieurs champs ou propriétés de l'entité classe (voir Section 9.1.15, «IdClass Annotation»).
- Si la classe de clé primaire composite est mappée à plusieurs champs ou propriétés de la classe d'entité, le noms des champs de clé primaire ou propriétés dans la classe de clé primaire et ceux de la classe d'entité doivent correspondent et leurs types doivent être les même.
IdClass
La classe de la clé primaire composite pourrait ressembler (par exemple, à une classe interne statique):
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
}
Et l'entité:
@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
}
L'annotation IdClass
mappe plusieurs champs sur la table PK.
EmbeddedId
La classe de la clé primaire composite pourrait ressembler (par exemple, à une classe interne statique):
@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
}
Et l'entité:
@Entity
class Time implements Serializable {
@EmbeddedId
private TimePK timePK;
private String src;
private String dst;
private Integer distance;
private Integer price;
//...
}
L'annotation @EmbeddedId
mappe une classe PK à la table PK.
@EmbeddedId
communique d'une manière ou d'une autre plus clairement que la clé est une clé composite et que l'OMI a du sens lorsque le pk combiné est une entité significative ou qu'il est réutilisé dans votre code.@IdClass
est utile pour spécifier que certaines combinaisons de champs sont uniques mais qu'elles n'ont pas de signification particulière.Ils affectent également la manière dont vous écrivez les requêtes (en les rendant plus ou moins verbeuses):
avec IdClass
select t.levelStation from Time t
avec EmbeddedId
select t.timePK.levelStation from Time t
Vous devez utiliser @EmbeddedId
:
@Entity
class Time {
@EmbeddedId
TimeId id;
String src;
String dst;
Integer distance;
Integer price;
}
@Embeddable
class TimeId implements Serializable {
Integer levelStation;
Integer confPathID;
}
Comme je l'ai expliqué dans cet article , en supposant que vous disposiez des tables de base de données suivantes:
Tout d’abord, vous devez créer le @Embeddable
contenant l’identifiant composite:
@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());
}
}
Avec ceci en place, nous pouvons mapper l'entité Employee
qui utilise l'identifiant composite en l'annotant avec @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;
}
}
L'entité Phone
qui a une association @ManyToOne
avec Employee
, doit référencer l'identificateur composite de la classe mère via deux mappages @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;
}
}
Pour plus de détails, consultez cet article .
On dirait que vous faites cela à partir de zéro. Essayez d'utiliser des outils de reverse engineering disponibles, tels que Netbeans Entities from Database, pour au moins automatiser les bases (comme les identifiants intégrés). Cela peut devenir un énorme mal de tête si vous avez plusieurs tables. Je suggère d'éviter de réinventer la roue et d'utiliser autant d'outils que possible pour réduire au minimum le codage, ce que vous avez l'intention de faire.
La classe de clé primaire doit définir les méthodes equals et hashCode}
Prenons un exemple simple. Supposons que deux tables nommées test
et customer
sont décrites comme suit:
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));
Il y a une autre table qui garde les traces de test
s et 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));
Nous pouvons voir que dans la table tests_purchased
la clé primaire est une clé composite, nous allons donc utiliser la balise <composite-id ...>...</composite-id>
dans le fichier de mappage hbm.xml
. Donc, le PurchasedTest.hbm.xml
ressemblera à:
<?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>
Mais cela ne s'arrête pas là. Dans Hibernate, nous utilisons session.load (entityClass
, id_type_object
) pour rechercher et charger l'entité à l'aide de la clé primaire. Dans le cas de clés composites, l'objet ID doit être une classe d'ID séparée (dans le cas précédent une classe PurchasedTestId
) qui déclare simplement les attributs de clé primaire comme ci-dessous :
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;
}
}
Le point important est que nous implémentons également les deux fonctions hashCode()
et equals()
, car Hibernate s’appuie sur elles.
Une autre option consiste à mapper en tant que mappe d'éléments composites dans la table ConfPath.
Ce mappage bénéficierait toutefois d'un index (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;
}
}
Cartographie:
<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>
Utilisation de 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>
Utiliser l'annotation
Classe de clés composites
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 );
}
}
Classe d'entité
@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
);
}
}