Hibernate的映射组件属性

时间:2023-03-09 09:44:57
Hibernate的映射组件属性

组件属性

如果持久化类的属性并不是基本数据类型,也不是一个日期或者字符串常量,而是一个复合类型的对象,例如 private Name name; 那么这就是一个组件属性。

组件属性可以是任何普通的java类对象,在映射文件中则用<compent>标签标识,<compent>中又可以像<class>那样包含<property>子元素,此外<compent>中还可以包含一个<parent>子元素用来指向容器实体。

下面演示组件属性的用法,比如在Person持久化类中有一个Name属性,

 package com;

 public class Person {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Name getName() {
return name;
}
public void setName(Name name) {
this.name = name;
}
private Integer id;
private int age;
private Name name;

}

name属性的类型Name只是一个普通的java类,

 package com;

 public class Name {
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
private String first;
private String last;
private Person owner;
public Name() { }
public Name(String first, String last) {
this.first = first;
this.last = last;
}
}

那么在person类的映射文件中,需要用<compent>表明name为一个组件属性,

 <?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com">
<class name="Person" table="Person_inf">
<id name="id" column="person_id" type="int">
<generator class="identity" />
</id>
<property name="age" type="int" />
<component name="name" class="Name" unique="true">
<parent name="owner"/>
<property name="first" />
<property name="last" />
</component>
</class>
</hibernate-mapping>

下面写一个测试类,

 package com;

 import java.util.HashMap;
import java.util.Map; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; public class PersonManager {
public static void exec(String resource, Object obj) {
// 打开线程安全的session
Configuration conf = new Configuration().configure();
conf.addResource("com/"+resource);
// 用Configuration创建SessionFactory
SessionFactory sf = conf.buildSessionFactory();
// 用SessionFactory打开Session
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.save(obj);
tx.commit();
sess.close();
sf.close();
} public static void ComponentTest() {
Person cp = new Person();
cp.setAge(30);
Name name = new Name("天王盖地虎","宝塔镇河妖");
cp.setName(name);
exec("Person.hbm.xml",cp);
} public static void main(String[] args) {
ComponentTest();
}
}

执行程序,会发现hibernate生成的person_inf表中,hibernate将组件属性name映射成了person_inf表中的两个列

Hibernate的映射组件属性

组件属性为集合

如果组件属性是一个集合,例如list, map, set等, 那么可以在<compent>中包含<list> <set> <map>等子元素。

还是以上面的person为例,假如person的name属性中,多了一个power属性,power是map类型,

 package com;

 public class MapPerson {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public MapName getName() {
return name;
}
public void setName(MapName name) {
this.name = name;
}
private Integer id;
private int age;
private MapName name; }
 package com;

 import java.util.HashMap;
import java.util.Map; public class MapName {
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
public MapName() { }
public MapName(String first, String last) {
this.first = first;
this.last = last;
} public Map<String, Integer> getPower() {
return power;
}
public void setPower(Map<String, Integer> power) {
this.power = power;
} private String first;
private String last;
private Person owner;
private Map<String, Integer> power = new HashMap<String, Integer>(); }

因此在person类的映射文件中,需要在<compent>标签里增加<map>子标签,

 <?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com">
<class name="MapPerson" table="Person_inf">
<id name="id" column="person_id" type="int">
<generator class="identity" />
</id>
<property name="age" type="int" />
<component name="name" class="MapName" unique="true">
<parent name="owner"/>
<property name="first" />
<property name="last" />
<map name="power" table="name_power">
<key column="person_name_id" not-null="true" />
<map-key column="name_aspect" type="string" />
<element column="name_power" type="int"/>
</map>
</component>
</class>
</hibernate-mapping>

在测试类中新增测试方法mapTest()

 public static void mapTest() {
MapPerson mp = new MapPerson();
mp.setAge(30);
MapName mn = new MapName("天王盖地虎","宝塔镇河妖");
Map<String, Integer> power = new HashMap<String, Integer>();
power.put("力量", 100);
power.put("速度", 90);
mn.setPower(power);
mp.setName(mn);
exec("MapPerson.hbm.xml",mp);
}

执行测试类,发现在mysql中,持久化类的属性为集合元素的时候,hibernate会将它映射成单独的一个表,并用外键关联持久化类。 其实这已经在映射文件中配置了。

person_inf表

Hibernate的映射组件属性

name_power表

Hibernate的映射组件属性

集合属性的元素为组件

这个跟前面的 “组件属性为集合”刚好反过来, 持久化类中的属性本身就是集合属性,在映射文件中依然用<list> <set> <map>等标签映射这些属性,不过对于这些属性中的值(集合元素),则用<composite-element>而不是<element>来映射。

还是以person类为例,假如person类中有个属性nicks,是map类型,

 package com;

 import java.util.HashMap;
import java.util.Map; public class MapComPerson {
private Integer id;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Map<String, MapName> getNicks() {
return nicks;
}
public void setNicks(Map<String, MapName> nicks) {
this.nicks = nicks;
}
private int age;
private Map<String, MapName> nicks = new HashMap<String, MapName>();
}

此时的Name类已经不再是person的属性,而是构成person属性nicks中的map元素值类型,

 package com;

 import java.util.HashMap;
import java.util.Map; public class MapName {
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
public MapName() { }
public MapName(String first, String last) {
this.first = first;
this.last = last;
} public Map<String, Integer> getPower() {
return power;
}
public void setPower(Map<String, Integer> power) {
this.power = power;
} private String first;
private String last;
private Person owner;
private Map<String, Integer> power = new HashMap<String, Integer>(); }

person类对应的映射文件,

 <?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com">
<class name="MapComPerson" table="Person_inf">
<id name="id" column="person_id" type="int">
<generator class="identity" />
</id>
<property name="age" type="int" />
<map name="nicks" table="nick_inf">
<key column="person_id" not-null="true" />
<map-key column="phase" type="string" />
<composite-element class="MapName">
<parent name="owner" />
<property name="first" />
<property name="last" />
</composite-element>
</map>
</class>
</hibernate-mapping>

在测试类中新增mapComponentTest()方法,

public static void mapComponentTest() {
MapComPerson mcp = new MapComPerson();
mcp.setAge(25); Map<String, MapName> nick = new HashMap<String, MapName>();
MapName mn = new MapName("天王盖地虎","宝塔镇河妖");
nick.put("小虎", mn);
mcp.setNicks(nick);
exec("MapComPerson.hbm.xml", mcp);
}

执行上面的测试类,发现在mysql中,当集合属性为组件的时候,Hibernate将集合属性放在单独表中,用外键关联主表,并将属性值映射成单独表中的列。

person_inf

Hibernate的映射组件属性

nick_inf

Hibernate的映射组件属性

组件作为map的索引(key)

对于集合类型,其key不仅可以是普通数据类型或者包装类型,例如String等,也可以是组件类型。Hibernate中使用<composite-map-key>来映射复合型的key。即每一个key又是另一种复合类的对象。这些复合类又会有自己的属性,

根据key复合类属性的类型,<composite-map-key>标签下又可以有两种子标签:

<key-property>,复合类属性为普通类型的数据,或者日期,字符串等。

<key-many-to-one>复合类属性引用了其他持久化类。

下面演示以组件作为map的索引,还是以person类为例,假如person类中有一个属性nickPower是map类型,其索引(key)的类型是Name,

 package com;

 import java.util.HashMap;
import java.util.Map; public class MapKeyPerson {
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
} public Map<MapKeyName, Integer> getNickPower() {
return nickPower;
}
public void setNickPower(Map<MapKeyName, Integer> nickPower) {
this.nickPower = nickPower;
} private Integer id;
private int age;
private Map<MapKeyName, Integer> nickPower = new HashMap<MapKeyName, Integer>();

}

这里的MapKeyName也只是一个普通的类,但是因为会作为Map集合的索引,所以必须以first和last这两个关键属性重写equals()和hashCode()两个方法,才能保证Map的正常工作

 package com;

 public class MapKeyName {
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
private String first;
private String last;
private Person owner;
public MapKeyName() { }
public MapKeyName(String first, String last) {
this.first = first;
this.last = last;
} public boolean equals(Object obj) {
if (this == obj) return true;
if (obj != null && obj.getClass() == MapKeyName.class) {
MapKeyName target = (MapKeyName)obj;
if (target.getFirst().equals(getFirst())
&& target.getLast().equals(getLast())) {
return true;
}
}
return false;
} public int hashCode() {
return getFirst().hashCode() * 13 + getLast().hashCode();
}
}

Person类的映射文件,

 <?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com">
<class name="MapKeyPerson" table="person_inf">
<id name="id" column="person_id" type="int">
<generator class="identity" />
</id>
<property name="age" type="int" />
<map name="nickPower" table="nick_Power">
<key column="person_id" not-null="true" />
<!-- 因为may-key是复合类型,所以使用下面元素 -->
<composite-map-key class="MapKeyName">
<key-property name="first" type="string" />
<key-property name="last" type="string" />
</composite-map-key>
<element column="nick_power" type="int" />
</map>
</class>
</hibernate-mapping>

在测试类中新增测试方法mapKeyTest(),

 public static void mapKeyTest() {
MapKeyPerson mkp = new MapKeyPerson();
mkp.setAge(25);
Map<MapKeyName, Integer> nickPower = new HashMap<MapKeyName, Integer>();
MapKeyName mkn = new MapKeyName();
mkn.setFirst("天王盖地虎");
mkn.setLast("宝塔镇河妖");
nickPower.put(mkn, 100);
mkp.setNickPower(nickPower);
exec("MapKeyPerson.hbm.xml", mkp);
}

执行上面的测试类,会发现Hibernate已经将MapKeyName组件的各种属性都映射到了集合属性表的各个列了,此时MapKeyName属性将作为map key使用,hibernate将外键列,MapKeyName各个属性一起作为联合主键。

person_inf

Hibernate的映射组件属性

nick_power

Hibernate的映射组件属性

nick_power表中的联合主键

Hibernate的映射组件属性

组件作为复合主键

上面的持久化类的主键通常都是普通数据类型,如 private int id, 主键也可以是复合类型,即以组件作为主键。那么在映射文件中,就以<composite-id>来映射,在<composite-id>标签下以<key-property>来映射组件属性。

同时,因为主键将使用一个组件类型来代替,这个组件类型需要实现 java.io.Serializable 接口,

例如在下面的持久化类中,用复合类型的name作为主键,

 package com;

 public class PrimaryKeyPerson {
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
} public PrimaryKeyName getName() {
return name;
}
public void setName(PrimaryKeyName name) {
this.name = name;
} private Integer id;
private int age;
private PrimaryKeyName name; }

此复合类PrimaryKeyName也需要重写equals()和hashCode()方法(以first和last为关键属性),这个组件类型需要实现 java.io.Serializable 接口,

 package com;

 public class PrimaryKeyName implements java.io.Serializable {
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
private String first;
private String last;
public PrimaryKeyName() {} public PrimaryKeyName(String first, String last) {
this.first = first;
this.last = last;
} public boolean equals(Object obj) {
if (this == obj) return true;
if (obj != null && obj.getClass() == PrimaryKeyName.class) {
PrimaryKeyName target = (PrimaryKeyName)obj;
return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast());
}
return false;
} public int hashCode() {
return this.getFirst().hashCode() * 7 + this.getLast().hashCode();
} }

person类的映射文件如下,用<composite-id>映射复合组件为主键,用子元素<key-property>映射复合组件中的属性,

 <?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com">
<class name="PrimaryKeyPerson" table="Person_inf">
<composite-id name="name" class="PrimaryKeyName">
<key-property name="first" type="string" />
<key-property name="last" type="string" />
</composite-id>
<property name="age" type="int" />
</class>
</hibernate-mapping>

在测试类中新增测试方法,PrimaryKeyTest(),

 public static void PrimaryKeyTest() {
PrimaryKeyPerson pkp = new PrimaryKeyPerson();
PrimaryKeyName name = new PrimaryKeyName();
name.setFirst("天王盖地虎");
name.setLast("宝塔镇河妖");
pkp.setAge(20);
pkp.setName(name);
exec("PrimaryKeyPerson.hbm.xml", pkp);
}

执行上面的测试代码,发现Hibernate将PrimaryKeyName组件的所有属性映射为person类的列,成为person表的联合主键,

person_inf

Hibernate的映射组件属性

person_inf表的联合主键。

Hibernate的映射组件属性

多列作为联合主键

如果将持久化类中的多列作为联合主键,hibernate也用<composite-id>来映射,同时持久化类本身需要实现 java.io.Serializable接口,情形与上面的组件为复合主键非常类似,

假如person类中以first和last作为联合主键,首先要让person类实现 java.io.Serializable接口,并且重写equals和hashCode方法,

 package com;

 public class UnionKeyPerson implements java.io.Serializable {
private String first;
private String last;
private int age; public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
} public boolean equals(Object obj) {
if (this == obj) return true;
if (obj != null && obj.getClass() == PrimaryKeyName.class) {
PrimaryKeyName target = (PrimaryKeyName)obj;
return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast());
}
return false;
} public int hashCode() {
return this.getFirst().hashCode() * 7 + this.getLast().hashCode();
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
} }

这种情况下就没有依赖的额外组件了,直接是持久化类和映射文件,映射文件如下,

 <?xml version="1.0"  encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com">
<class name="UnionKeyPerson" table="Person_inf">
<composite-id>
<key-property name="first" type="string" />
<key-property name="last" type="string" />
</composite-id>
<property name="age" type="int" />
</class>
</hibernate-mapping>

映射文件也与前面的组件为联合主键非常类似,只不过这里的<composite-id>不需要额外映射组件类了,而是直接用持久化类中的属性,因此不再需要name和class标签了。

在测试类中新增UnionKeyTest()方法,

 public static void UnionKeyTest() {
UnionKeyPerson ukp = new UnionKeyPerson();
ukp.setFirst("天王盖地虎");
ukp.setLast("宝塔镇河妖");
ukp.setAge(30);
exec("UnionKeyPerson.hbm.xml",ukp);
}

执行测试类,表结构与上面的完全相同,这里将以属性first和last作为联合主键,

Hibernate的映射组件属性

使用JPA Annotation标注实体

在实际应用中,使用Annotation代替XML映射文件的情形更多,因为使用XML文件需要维护两个文件,而使用Annotation只需要维护一个文件。这些Annotation是JPA标准,与Hibernate无关,具体要求可以参考JavaEE规范的API。

如果将前面的person类改成Annotation方式来映射,将会是下面这样,

@Entity用来标注该类是一个持久化类,

@EmbeddedId用来标注复合类型的标识属性

@EmbeddedId用来标注组件属性

 package com;

 import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table; @Entity
@Table(name="person_table")
public class AnnotationPerson {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name="first", column=@Column(name="person_first")),
@AttributeOverride(name="last", column=@Column(name="person_last", length=20))
})
private AnnotationName name;
@Column(name="person_email")
private String email;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="name", column=@Column(name="cat_name", length=35)),
@AttributeOverride(name="color", column=@Column(name="cat_color"))
})
private AnnotationCat pet;
public AnnotationName getName() {
return name;
}
public void setName(AnnotationName name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public AnnotationCat getPet() {
return pet;
}
public void setPet(AnnotationCat pet) {
this.pet = pet;
}
}

上面持久化类中的name属性是一个组件属性,需要定义这个属性,在组件类名上面加上@Embeddable注解即可。

 package com;

 import javax.persistence.Embeddable;

 @Embeddable
public class AnnotationName implements java.io.Serializable {
private String first;
private String last; public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public AnnotationName() {}
public AnnotationName(String first, String last) {
this.first = first;
this.last = last;
}
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj != null && obj.getClass() == PrimaryKeyName.class) {
PrimaryKeyName target = (PrimaryKeyName)obj;
return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast());
}
return false;
}
public int hashCode() {
return this.getFirst().hashCode() * 17 + this.getLast().hashCode();
} }

上面的pet也是一个属性组件,同样需要定义这个类,跟前面的name属性一样,

 package com;

 import javax.persistence.Embeddable;

 @Embeddable
public class AnnotationCat {
public AnnotationCat() {}
public AnnotationCat(String name, String color) {
this.name = name;
this.color = color;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
private String color;
}

有了上面这些持久化类和组件类之后即注解之后,就不再需要额外的映射文件了,在hibernate.cfg.xml中,

可以用<mapping class="xxxx/AnnotationPerson" />来加载持久化类,当然也可以直接在java代码中,用addAnnotatedClass()方法直接加载带注解的持久化类,

下面在测试类中新增测试方法annotationTest(),

 public static void annotationTest() {
AnnotationPerson ap = new AnnotationPerson();
ap.setEmail("xxx@baidu.com");
AnnotationName name = new AnnotationName();
name.setFirst("天王盖地虎");
name.setLast("宝塔镇河妖");
ap.setName(name);
AnnotationCat pet = new AnnotationCat();
pet.setName("miaomiao");
pet.setColor("black");
ap.setPet(pet); Configuration conf = new Configuration().configure();
conf.addAnnotatedClass(AnnotationPerson.class);
SessionFactory sf = conf.buildSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.save(ap);
tx.commit();
sess.close();
sf.close();
}

执行测试类,可以看到hibernate将组件属性映射为表中的不同列了,并将name属性作为了联合主键,其效果跟用XML实现的映射文件完全一样。

person_table

Hibernate的映射组件属性

person_table表结构

Hibernate的映射组件属性