zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    什么是工厂模式
    冒泡算法
    CSS中的绝对定位与相对定位
    JS function立即调用的几种写法
    paip.java 线程无限wait的解决
    paip.java 多线程参数以及返回值Future FutureTask 的使用.
    PAIP.并发编程 多核编程 线程池 ExecutorService的判断线程结束
    paip.slap工具与于64位win7与JDBC的性能对比
    JProfiler8 注册码序列号
    paip.提升性能---mysql 优化cpu多核以及lan性能的关系.
  • 原文地址:https://www.cnblogs.com/yangang2013/p/5528382.html
Copyright © 2011-2022 走看看