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

一、单向n-1

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

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

     image

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

imageimage

 

二、双向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。

 

测试:

image

结果:

     class org.hibernate.collection.PersistentSet

 

2.cascade属性

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

     image

Customer.xml

     image

测试代码:

          image 

结果:

      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关联关系

     image

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,否则会造成主键冲突。

     image

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);

结果:

imageimageimage

 

五、映射继承关系

    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);

结果:

     image

 

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);

结果:

     imageimage

 

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);

结果:

     imageimage

posted @ 2016-05-25 20:24  ✈✈✈  阅读(1443)  评论(0编辑  收藏  举报