Hibernate基础学习(五)—对象-关系映射(下)

时间:2022-02-03 17:01:22

一、单向n-1

单向n-1关联只需从n的一端可以访问1的一端。

域模型: 从Order到Customer的多对一单向关联。Order类中定义一个Customer属性,而在Customer类不用存放Order对象的引用。

     Hibernate基础学习(五)—对象-关系映射(下)

Order.java

public class Order{

	private Integer uid;
private String name; private Customer cus; //省略get、set方法
}

Customer.java

public class Customer{

	private Integer uid;
private String username; //省略get、set方法
}

Customer.hbm.xml

<hibernate-mapping>

	<class name="com.kiwi.domain.Customer" table="t_cus">

		<id name="uid">
<generator class="native"/>
</id> <property name="username"/> </class> </hibernate-mapping>

Order.hbm.xml

<hibernate-mapping>

	<class name="com.kiwi.domain.Order" table="t_order">

		<id name="uid">
<generator class="native"/>
</id> <property name="name"/> <many-to-one name="cus" class="com.kiwi.domain.Customer" column="cus_id"/> </class> </hibernate-mapping>

Test.java

			Customer c = new Customer();
c.setUsername("AAA"); Order o1 = new Order();
o1.setName("order1");
Order o2 = new Order();
o2.setName("order2"); //设置关联关系
o1.setCus(c);
o2.setCus(c); //顺序保存执行3条insert语句
session.save(c);
session.save(o1);
session.save(o2);

结果:

t_cus                    t_order

Hibernate基础学习(五)—对象-关系映射(下)Hibernate基础学习(五)—对象-关系映射(下)

 

二、双向1-n

双向1-n和双向n-1是完全相同的情形。

Order.java

public class Order{

	private Integer uid;
private String name; private Customer cus; //省略get set方法
}

Customer.java

public class Customer{

	private Integer uid;
private String username; private Set<Order> orders = new HashSet<Order>(); //省略get set方法
}

Customer.hbm.xml

<hibernate-mapping>

	<class name="com.kiwi.domain.Customer" table="t_cus">

		<id name="uid">
<generator class="native" />
</id> <property name="username" /> <!-- 1对多的映射 -->
<set name="orders" table="t_order">
<key column="cus_id" />
<one-to-many class="com.kiwi.domain.Order" />
</set> </class> </hibernate-mapping>

Order.hbm.xml

<hibernate-mapping>

	<class name="com.kiwi.domain.Order" table="t_order" >

		<id name="uid">
<generator class="native"/>
</id> <property name="name"/> <many-to-one name="cus" class="com.kiwi.domain.Customer" column="cus_id"/> </class> </hibernate-mapping>

Test.java

			Customer c = new Customer();
c.setUsername("BBB"); Order o1 = new Order();
o1.setName("order3");
Order o2 = new Order();
o2.setName("order4"); //设置关联关系
o1.setCus(c);
o2.setCus(c); c.getOrders().add(o1);
c.getOrders().add(o2); //3条insert语句,2条update语句
//因为两端都维护关联关系,所以会多update语句
session.save(c);
session.save(o1);
session.save(o2);

结果:

      发现执行了3条insert语句后,又执行了2条update语句。原因是由于两端都维护了关联而导致的,所以我们设置一端维护关联关系即可,使用inverse属性。   

 

1.元素的inverse属性

     (1)在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系。

     (2)inverse = false 的为主动方,inverse = true 的为被动方,由主动方负责维护关联关系。

     (3)在没有设置 inverse=true 的情况下,父子两边都维护父子关系。

     (4)在1-n关系中,将n方设为主控方将有助于性能改善。

     (5)在1-n 关系中,若将1方设为主控方,会额外多出update语句。

     建议设定set的inverse=true,先插入1的一端,再插入多的一端不会多出update语句。

Customer.hbm.xml

<hibernate-mapping>

	<class name="com.kiwi.domain.Customer" table="t_cus">

		<id name="uid">
<generator class="native" />
</id> <property name="username" /> <!-- 1对多的映射 -->
<set name="orders" table="t_order" inverse="true">
<key column="cus_id" />
<one-to-many class="com.kiwi.domain.Order" />
</set> </class> </hibernate-mapping>

 

     (6)当Session从数据库中加载Java集合时, 创建的是Hibernate内置集合类的实例, 因此在持久化类中定义集合属性时必须把属性声明为Java接口类型。

     (7)Hibernate的内置集合类具有集合代理功能, 支持延迟检索策略。

    (8)事实上, Hibernate 的内置集合类封装了JDK中的集合类, 这使得Hibernate能够对缓存中的集合对象进行脏检查, 按照集合对象的状态来同步更新数据库。

    (9)在定义集合属性时, 通常把它初始化为集合实现类的一个实例. 这样可以提高程序的健壮性, 避免应用程序访问取值为null 的集合的方法抛出NullPointerException。

 

测试:

Hibernate基础学习(五)—对象-关系映射(下)

结果:

     class org.hibernate.collection.PersistentSet

 

2.cascade属性

     在对象-关系映射文件中,用于映射持久化类之间关联关系的元素,<set>,<many-to-one>和<one-to-one>都有一个 cascade 属性,它用于指定如何操纵与当前对象关联的其他对象。

     Hibernate基础学习(五)—对象-关系映射(下)

Customer.xml

     Hibernate基础学习(五)—对象-关系映射(下)

测试代码:

          Hibernate基础学习(五)—对象-关系映射(下) 

结果:

      Hibernate: insert into t_customer (cname) values (?)

      Hibernate: insert into t_order (price, customer_id) values (?, ?)

      Hibernate: insert into t_order (price, customer_id) values (?, ?)

 

三、1-1关联关系

     Hibernate基础学习(五)—对象-关系映射(下)

1.基于外键映射1-1

     对于基于外键的1-1关联,存在外键的一方可认为是1-n的特殊情况,增加many-to-one元素,并且增加"unique=true"属性来表示1-1关联。

     另一端使用one-to-one元素,该元素使用property-ref属性指定对方映射中外键列对应的属性名。

Person.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Person" table="t_person"> <id name="uid">
<generator class="native"/>
</id> <property name="name" type="string"/>
<property name="age" type="integer"/> <!--
idCard属性 IdCard类型
本类与IdCard的1对1关系。本方无外键方
property-ref:对方映射中外键列对应的属性名
-->
<one-to-one name="idCard" class="com.kiwi.domain.IdCard" property-ref="person" /> </class> </hibernate-mapping>

IdCard.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.IdCard" table="t_idcard"> <id name="uid">
<generator class="native"/>
</id> <property name="no"/> <!--
person属性 Person类型
本类与person的1对1关系。本方有外键方
-->
<many-to-one name="person" class="com.kiwi.domain.Person" column="personId" unique="true" /> </class> </hibernate-mapping>

Test.java

public class TestDemo{

	//测试保存对象
@Test
public void testSava(){ Session session = HibernateUtils.getSession();
Transaction tx = null; try{
tx = session.beginTransaction(); Person p = new Person();
p.setAge(18);
p.setName("李四"); IdCard i = new IdCard();
i.setNo("4128231992000002"); p.setIdCard(i);
i.setPerson(p); session.save(p);
session.save(i); tx.commit();
}catch(RuntimeException e){
tx.rollback();
throw e;
}finally{
session.close();
} } //解除关联关系: 在1-1中,只能有外键方可以维护关联关系
@Test
public void testRelation(){ Session session = HibernateUtils.getSession();
Transaction tx = null; try{
tx = session.beginTransaction(); //从无外键方解除关系: 不可以
Person p = (Person)session.get(Person.class,1);
p.setIdCard(null); //从有外键方解除关系: 可以
IdCard i = (IdCard)session.get(IdCard.class,1);
i.setPerson(null); tx.commit();
}catch(RuntimeException e){
tx.rollback();
throw e;
}finally{
session.close();
} } //删除对象 对关联关系的影响
@Test
public void testDelete(){ Session session = HibernateUtils.getSession();
Transaction tx = null; try{
tx = session.beginTransaction(); //a.如果没有关联对方,能删除
//b.如果有关联的对方,但是不能维护关联关系,所以会先删自己,有异常
//c.如果有关联的对方,而且可以维护关联关系,它会先删除关联关系再删除自己 Person p = (Person)session.get(Person.class,1);
IdCard i = (IdCard)session.get(IdCard.class,1);
//会抛异常
session.delete(p);
//先解除关联关系,然后删掉自己
session.delete(i); tx.commit();
}catch(RuntimeException e){
tx.rollback();
throw e;
}finally{
session.close();
}
} }

注意:

(1)在1-1关联关系中,只有外键方能维护关联关系,从无外键方解除关联关系不可以,从有外键方解除关联关系可以。

(2)在1-1关联关系中,执行删除操作。如果没有关联关系,能直接删除;如果有关联关系,但是不能维护关联关系,会出异常。如果有关联关系,而且能维护关联关系,能直接删除自己。

 

2.基于主键映射1-1

基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据"对方"的主键来生成自己的主键,自己并不能独立生成主键. <param> 子元素指定使用当前持久化类的哪个属性作为"对方"。

采用foreign主键生成器策略的一端增加one-to-one元素映射关联属性,其one-to-one属性还应增加constrained="true" 属性;另一端增加one-to-one元素映射关联属性。

constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象("对方")所对应的数据库表主键。

Person.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Person" table="t_person"> <id name="uid">
<generator class="native"/>
</id> <property name="name" type="string"/>
<property name="age" type="integer"/> <!--
idCard属性 IdCard类型
本类与IdCard的1对1关系。本方无外键方
-->
<one-to-one name="idCard" class="com.kiwi.domain.IdCard"/> </class> </hibernate-mapping>

IdCard.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.IdCard" table="t_idcard"> <id name="uid">
<!--当使用基于主键1-1映射时
有外键方的主键生成策略一定要是foreign
property:生成主键值所根据的对象
-->
<generator class="foreign">
<param name="property">person</param>
</generator>
</id> <property name="no"/> <!--
person属性 Person类型
本类与person的1对1关系。本方有外键方
-->
<one-to-one name="person" class="com.kiwi.domain.Person" constrained="true"/> </class> </hibernate-mapping>

 

四、双向n-n

(1)双向n-n需要两端都使用集合属性。

(2)双向n-n必须使用中间表。

(3)双向n-n默认都维护关联关系的,所以必须把其中一端的inverse设置为true,否则会造成主键冲突。

     Hibernate基础学习(五)—对象-关系映射(下)

Student.java

public class Student{

	private Long uid;
private String name; private Set<Teacher> teachers = new HashSet<Teacher>(); //省略get、set方法...
}

Teacher.java

public class Teacher{

	private Long uid;
private String name; private Set<Student> students = new HashSet<Student>(); //省略get、set方法...
}

Student.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Student" table="t_stu"> <id name="uid">
<generator class="native"/>
</id> <property name="name"/> <!--
teachers属性 set集合
本类与Teacher的n-n关联关系
table:中间表(集合表)
key: 集合外键(引用当前表主键的那个外键)
--> <set name="teachers" table="teach_stu" inverse="true">
<key column="stu_id"/>
<many-to-many class="com.kiwi.domain.Teacher" column="teacher_id"/>
</set> </class> </hibernate-mapping>

Teacher.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Teacher" table="t_teacher"> <id name="uid">
<generator class="native"/>
</id> <property name="name"/> <!--
students属性 set集合
本类与Student的n-n关联关系
-->
<set name="students" table="teach_stu">
<key column="teacher_id"/>
<many-to-many class="com.kiwi.domain.Student" column="stu_id"/>
</set> </class> </hibernate-mapping>

Test.java

			Student s1 = new Student();
s1.setName("学生1");
Student s2 = new Student();
s2.setName("学生2"); Teacher t1 = new Teacher();
t1.setName("老师1");
Teacher t2 = new Teacher();
t2.setName("老师2"); s1.getTeachers().add(t1);
s1.getTeachers().add(t2);
s2.getTeachers().add(t1);
s2.getTeachers().add(t2);
t1.getStudents().add(s1);
t1.getStudents().add(s2);
t2.getStudents().add(s1);
t2.getStudents().add(s2); session.save(s1);
session.save(s2);
session.save(t1);
session.save(t2);

结果:

Hibernate基础学习(五)—对象-关系映射(下)Hibernate基础学习(五)—对象-关系映射(下)Hibernate基础学习(五)—对象-关系映射(下)

 

五、映射继承关系

    Hibernate的继承映射可以理解持久化类之间的基础关系。例如: 人和学生之间的关系,学生继承了人,可以认为学生是一种特殊的人,如果对人进行查询,学生的实例也将被得到。

     Hibernate支持三种继承关系:

     使用subclass进行映射

     使用joined-subclass进行映射

     使用union-subclass进行映射

 

1.使用subclass进行映射

(1)可以实现对继承关系中的父类和子类使用同一张表

(2)因为父类和子类的实例全部保存在同一张表,因此需要在该表内增加一列,使用该列来区分每行记录属于哪个类的实例,这个列被称为辨别者列(discriminator)。

(3)在这种映射策略下,使用subclass来映射子类,使用class和subclass的discriminator-value属性来指定辨别者列的值。

(4)所有的子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中。

Person.java

public class Person{

	private Long uid;
private String name;
private int age; //省略get、set方法...
}

Student.java

public class Student extends Person{

	private String school;

	//省略get、set方法...
}

Person.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Person" table="t_person" discriminator-value="person"> <id name="uid">
<generator class="native" />
</id> <!-- 添加辨别者列 -->
<discriminator column="type" type="string" /> <property name="name" />
<property name="age" /> <!-- 使用 subclass 来映射子类 -->
<subclass name="com.kiwi.domain.Student" discriminator-value="stu">
<property name="school" type="string" />
</subclass> </class> </hibernate-mapping>

Test.java

			Person p = new Person();
p.setAge(11);
p.setName("person1"); Student s = new Student();
s.setAge(22);
s.setName("studnet1");
s.setSchool("QingHua"); session.save(p);
session.save(s);

结果:

     Hibernate基础学习(五)—对象-关系映射(下)

 

2.使用joined-subclass进行映射

(1)采用 joined-subclass 元素的继承映射可以实现每个子类一张表。

(2)采用这种映射策略时,父类实例保存在父类表中,子类实例由父类表和子类表共同存储。因为子类实例也是一个特殊的父类实例,因此必然也包含了父类实例的属性。于是将子类和父类共有的属性保存在父类表中,子类增加的属性,则保存在子类表中。

(3)在这种映射策略下,无须使用辨别者列,但需要为每个子类使用 key 元素映射共有主键。

(4)子类增加的属性可以添加非空约束。因为子类的属性和父类的属性没有保存在同一个表中。

 

Person.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Person" table="t_person" > <id name="uid">
<generator class="native" />
</id> <property name="name" />
<property name="age" /> <!-- 使用 joined-subclass 来映射子类 -->
<joined-subclass name="com.kiwi.domain.Student" table="t_stu">
<key column="stu_id"/>
<property name="school" type="string" />
</joined-subclass> </class> </hibernate-mapping>

Test.java

			Person p = new Person();
p.setAge(11);
p.setName("person1"); Student s = new Student();
s.setAge(22);
s.setName("studnet1");
s.setSchool("QingHua"); session.save(p);
session.save(s);

结果:

     Hibernate基础学习(五)—对象-关系映射(下)Hibernate基础学习(五)—对象-关系映射(下)

 

3.使用union-subclass进行映射

(1)采用union-subclass元素可以实现将每一个实体对象映射到一个独立的表中。

(2)子类增加的属性可以有非空约束 --- 即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。

(3)子类实例的数据仅保存在子类表中, 而在父类表中没有任何记录。

(4)在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段、加子类增加属性的总和

(5)在这种映射策略下,既不需要使用鉴别者列,也无须使用key元素来映射共有主键。

(6)使用union-subclass映射策略是不可使用identity的主键生成策略。

 

Person.hbm.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.kiwi.domain.Person" table="t_person" > <id name="uid">
<!-- 使用union-subclass映射策略是不可使用identity的主键生成策略 -->
<generator class="hilo">
<param name="table">hilo_table</param>
<param name="column">next_value</param>
<param name="max_lo">10</param>
</generator>
</id> <property name="name" />
<property name="age" /> <!-- 使用 union-subclass 来映射子类 --> <union-subclass name="com.kiwi.domain.Student" table="t_stu">
<property name="school" type="string" />
</union-subclass> </class> </hibernate-mapping>

Test.java

			Person p = new Person();
p.setAge(11);
p.setName("person1"); Student s = new Student();
s.setAge(22);
s.setName("studnet1");
s.setSchool("QingHua"); session.save(p);
session.save(s);

结果:

     Hibernate基础学习(五)—对象-关系映射(下)Hibernate基础学习(五)—对象-关系映射(下)