Hibernate学习笔记 | 详解映射关系(一对一、一对多、多对多、继承映射)

映射一对多关联关系

  • 在领域模型中,类与类之间最普遍的关系就是关联关系。
  • 在UML中,关联是有方向的。
    CustomerOrder为例:一个用户能发出多个订单,而一个订单只能属于一个客户,从OrderCustomer的关联是多对一的关联,而从CustomerOrder是一对多的关联。

单向n-1

  • 单向n-1关联只需从n端可以访问1端。
  • 域模型:从OrderCustomer的多对一单向关联需要在Order类中定义一个Customer属性,而在Customer类中无需定义存放Order对象的集合属性。
  • 关系数据模型:orders表中的customer_id参照customer表的主键。
  • 在Hibernate映射文件中使用<many-to-one>标签可以来映射多对一的关联关系,其标签有如下属性:
    name:一的那一端的属性的名字
    class:一的那一端对应的类名
    column:一的那一端在多的一端中对应的数据表中的外键的名字

在此以Order和Customer为例,Order与Customer为多对一关系。首先我们建立Order类和Customer类,如下:

package com.cerr.hibernate.n21;
public class Order {
    private Integer orderId;
    private String orderName;
    private Customer customer;
    public Integer getOrderId() {
        return orderId;
    }
    public void setOrderId(Integer orderId) {
        this.orderId = orderId;
    }
    public String getOrderName() {
        return orderName;
    }
    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }
    public Customer getCustomer() {
        return customer;
    }
    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
}
package com.cerr.hibernate.n21;
public class Customer {
    private Integer customerId;
    private String customerName;
    public Integer getCustomerId() {
        return customerId;
    }
    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }
    public String getCustomerName() {
        return customerName;
    }
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
}

IDEA只支持由数据表映射出实体类和映射文件,而我们此时是要通过实体类和映射文件来生成数据表,因此映射文件应该我们自己来新建。因此我们新建Customer.hbm.xml文件和Order.hbm.xml文件。映射信息如下:
Customer.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n21.Customer" table="customers" schema="hibernate5" dynamic-update="true">
        <id name="customerId" column="customer_id">
            <generator class="native" />
        </id>
        <property name="customerName" column="customer_name"/>

    </class>
</hibernate-mapping>

Order.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n21.Order" table="orders" schema="hibernate5" dynamic-update="true">
        <id name="orderId" column="order_id">
            <generator class="native" />
        </id>
        <property name="orderName" column="order_name"/>

        <!-- 映射多对一的关联关系 -->
        <many-to-one name="customer" class="com.cerr.hibernate.n21.Customer" column="customer_id"></many-to-one>

    </class>
</hibernate-mapping>

保存操作的测试类:

package com.cerr.hibernate.n21;

import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;

public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }

    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }
    @org.junit.Test
    public void test4(){
        Customer customer = new Customer();
        customer.setCustomerName("aa");
        Order order = new Order();
        order.setOrderName("1");
        Order order1 = new Order();
        order1.setOrderName("2");
        //设定关联关系
        order.setCustomer(customer);
        order1.setCustomer(customer);
        //先插入1的一端,再插入n的一端。只有三条insert语句。
        session.save(customer);
        session.save(order);
        session.save(order1);
        //如果先插入n的一端,再插入1的一端,则会多出来num(n的一端)条update语句。
//        session.save(order);
//        session.save(order1);
//        session.save(customer);
    }
    }

在测试类中我们分别新建了两个Order类和一个Customer类,并且在调用save()时我们使用了两种情况,一种情况是先保存Customer类(即先保存1),再保存Order类(再保存n)。另一种是先保存Order类(先保存n),再保存Customer类(再保存1)。两种结果都会插入成功,但是第一种的话只会生成3条INSERT语句,但是第二种会生成3条INSERT语句和2条UPDATE语句。因为先插入n的一端时,无法确定1的一端的外键值,所以只能先进行插入,然后等1的一端插入之后,再额外的发送UPDATE去修改n的一端的外键属性。
所以在插入的时候我们推荐先插入1的那一端,再插入n的那一端。
生成的数据表如下:


查询操作的测试类:

package com.cerr.hibernate.n21;

import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;

public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }

    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

    @org.junit.Test
    public void test5(){
        //若查询n的一端的对象,默认情况下只查询n的一端的对象,而没有查询1的那一端的对象
        Order order = session.get(Order.class,1);
        System.out.println(order);
        //在需要使用关联的对象时,才会发送对应的sql语句。
        Customer customer = order.getCustomer();
        System.out.println(customer);
    }
}

会发现若查询n的一端的对象,默认情况下只查询n的一端的对象,而没有查询1的那一端的对象,只有在需要使用到关联对象时,才会发送对应的sql语句,而如果在查询n端之后把session关了,才来使用关联对象(Customer)会导致LazyInitializationException异常
在获取Order对象后,打印其关联的Customer对象,发现其关联的Customer对象是一个代理对象。

使用删除操作的话,在没有设定级联关系的情况下,且1端的对象有n端对象在引用它(即存在n-1的映射关系),不能直接删除1端的对象。

双向1-n

双向1-n与双向n-1是完全相同的两种情形。双向1-n需要在1端可以访问n端,反之亦然。

  • 域模型:以OrderCustomer为例,从OrderCustomer的多对一双向关联需要在Order类中定义一个Customer属性,而在Customer类中需定义存放Order对象的集合属性。
  • 关系数据模型:orders表中的customer_id参照customer表的主键。

我们在上述的Order类和Customer类中修改,因为此时一端需要有一个Set集合来维护n端的Order,因此在Customer类中多出一个属性也就是一个维护n端的集合。

对于这个集合要注意两点:

  • 对于这个Set集合类型,在声明时,要使用接口类型,因为hibernate在获取集合类型时,返回的是hibernate内置的集合类型,而不是javase的标准集合实现。
  • 为了防止空指针异常,在声明集合的时候就要把集合给初始化。

Customer类如下:

package com.cerr.hibernate.n21.both;

import java.util.HashSet;
import java.util.Set;

public class Customer {
    private Integer customerId;
    private String customerName;

    //维护Order的集合
    //需要把集合做初始化,防止出现空指针异常。
    private Set<Order> orders = new HashSet <>();

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

    public Set < Order > getOrders() {
        return orders;
    }

    public void setOrders(Set < Order > orders) {
        this.orders = orders;
    }
}

Order类没有改变,Order.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n21.both.Order" table="orders" schema="hibernate5" dynamic-update="true">
        <id name="orderId" column="order_id">
            <generator class="native" />
        </id>
        <property name="orderName" column="order_name"/>

        <!-- 映射关系 -->
        <many-to-one name="customer" class="com.cerr.hibernate.n21.both.Customer" column="customer_id"></many-to-one>

    </class>
</hibernate-mapping>

对于映射文件新增的<set>标签的属性:

  • table:该值要和n-1中的n端的数据表名字一致。
  • <key>子标签:指定n的表中的外键列的名字
  • inverse:指定由哪一方来维护关联关系,通常在一端的映射文件中设置为true,表明由n端来维护关联关系。
  • cascade:设置级联操作。
  • order-by:在查询时对集合中的元素进行排序,order-by中使用的是表的字段名,而不是持久化类的属性名。

Customer.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n21.both.Customer" table="customers" schema="hibernate5" dynamic-update="true">
        <id name="customerId" column="customer_id">
            <generator class="native" />
        </id>
        <property name="customerName" column="customer_name"/>

        <!-- 映射1对多的那个集合属性 -->
        <set name="orders" table="orders" cascade="delete">
            <key column="customer_id"></key>
            <one-to-many class="com.cerr.hibernate.n21.both.Order"/>
        </set>
    </class>
</hibernate-mapping>

保存操作的测试类:

package com.cerr.hibernate.n21.both;

import com.cerr.hibernate.helloworld.News;
import com.cerr.hibernate.helloworld.Pay;
import com.cerr.hibernate.helloworld.Worker;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.junit.After;
import org.junit.Before;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Date;

public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }

    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

    @org.junit.Test
    public void test4(){
        Customer customer = new Customer();
        customer.setCustomerName("aa");
        Order order = new Order();
        order.setOrderName("1");
        Order order1 = new Order();
        order1.setOrderName("2");
        //设定关联关系
        order.setCustomer(customer);
        order1.setCustomer(customer);
        customer.getOrders().add(order);
        customer.getOrders().add(order1);
        //先插入1的一端,再插入n的一端。三条insert语句和2条UPDATE。
        session.save(customer);
        session.save(order);
        session.save(order1);
        //如果先插入n的一端,再插入1的一端,则会多出来2条update语句。合计三条INSERT语句,4条update
//        session.save(order);
//        session.save(order1);
//        session.save(customer);
    }
}

save()时,发现如果先插入一端,再插入n端的话,会发送三条INSERT语句和两条UPDATE语句,因为此时一端和n端都维护关联关系,所以会多出UPDATE语句。可以在1端的<set>节点中指定inverse=true来使一端放弃维护关联关系。

补充后的Customer.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n21.both.Customer" table="customers" schema="hibernate5" dynamic-update="true">
        <id name="customerId" column="customer_id">
            <generator class="native" />
        </id>
        <property name="customerName" column="customer_name"/>

        <!-- 映射1对多的那个集合属性 -->
        <set name="orders" table="orders" inverse="true">
            <key column="customer_id"></key>
            <one-to-many class="com.cerr.hibernate.n21.both.Order"/>
        </set>
    </class>
</hibernate-mapping>

对于查询操作,查n端的情况如上述单向n-1的情况一样。我们来看查一端的。
测试类:

@org.junit.Test
public void test6(){
    //对n端的集合使用延迟加载
    Customer customer = session.get(Customer.class,1);
    System.out.println(customer.getCustomerName());
    //集合返回的是Hibernate内置的集合类型
    //该类型具有延迟加载和存放代理对象的功能。
    System.out.println(customer.getOrders().getClass().getName());
}

查询一端时,如果没有用到一端中关联n端的集合的话,它也只会去查一端的对应表,而不会查n端的对应的数据表,因此对n端也是使用延迟加载,再需要用到那个关联n端的集合时才会去查表。并且该集合返回的类型是Hibernate内置的集合类型,该类型具有延迟加载和存放代理对象的功能。在关闭session后再去获取集合时也会发生LazyInitializationException异常

在删除时也要注意级联操作,跟上面的单向n-1一样。


映射一对一关联关系

  • 域模型
    以部门Department和经理Manager为例,假设一个部门只有一个经理,一个经理只能管一个部门,这样就形成了一个一对一的关联关系。
  • 关系数据模型
    有两种。一种是按照外键映射,另一种是按照主键映射。

基于外键映射的一对一关联关系

对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键的一端增加<many-to-one>元素,为<many-to-one>元素添加一个unique=true属性来表示为1-1关联。

另一端需要使用<one-to-one>元素,该元素使用property-ref属性指定使用被关联实体主键以外的字段作为关联属性

  • 没有使用该属性的SQL语句:
    select
        manager0_.mgr_id as mgr_id1_1_0_,
        manager0_.mgr_name as mgr_name2_1_0_,
        department1_.dept_id as dept_id1_0_1_,
        department1_.dept_name as dept_nam2_0_1_,
        department1_.mgr_id as mgr_id3_0_1_ 
    from
        manager manager0_ 
    left outer join
        //这里错了
        departments department1_ 
            on manager0_.mgr_id=department1_.dept_id 
    where
        manager0_.mgr_id=?
  • 使用该属性后的SQL:
    select
        manager0_.mgr_id as mgr_id1_1_0_,
        manager0_.mgr_name as mgr_name2_1_0_,
        department1_.dept_id as dept_id1_0_1_,
        department1_.dept_name as dept_nam2_0_1_,
        department1_.mgr_id as mgr_id3_0_1_ 
    from
        manager manager0_ 
    left outer join
        //这里没错
        departments department1_ 
            on manager0_.mgr_id=department1_.mgr_id 
    where
        manager0_.mgr_id=?

对于上述的例子,我们首先新建Department类和Manager类,如下:

package com.cerr.hibernate.one2one.foreign;
public class Department {
    private Integer deptId;
    private String deptName;
    private Manager mgr;
    public Integer getDeptId() {
        return deptId;
    }
    public void setDeptId(Integer deptId) {
        this.deptId = deptId;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    public Manager getMgr() {
        return mgr;
    }
    public void setMgr(Manager mgr) {
        this.mgr = mgr;
    }
}
package com.cerr.hibernate.one2one.foreign;
public class Manager {
    private Integer mgrId;
    private String mgrName;
    private Department dept;
    public Integer getMgrId() {
        return mgrId;
    }
    public void setMgrId(Integer mgrId) {
        this.mgrId = mgrId;
    }
    public String getMgrName() {
        return mgrName;
    }
    public void setMgrName(String mgrName) {
        this.mgrName = mgrName;
    }
    public Department getDept() {
        return dept;
    }
    public void setDept(Department dept) {
        this.dept = dept;
    }
}

编写映射文件
Department.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.cerr.hibernate.one2one.foreign.Department" table="departments" schema="hibernate5" dynamic-update="true">
        <id name="deptId" column="dept_id">
            <generator class="native" />
        </id>
        <property name="deptName" column="dept_name"/>

        <!-- 使用many-to-one的方式来映射1-1关联关系-->
        <many-to-one name="mgr" class="com.cerr.hibernate.one2one.foreign.Manager"
                     column="mgr_id" unique="true"></many-to-one>

    </class>
</hibernate-mapping>

Manager.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.cerr.hibernate.one2one.foreign.Manager" table="manager" schema="hibernate5" dynamic-update="true">
        <id name="mgrId" column="mgr_id">
            <generator class="native" />
        </id>
        <property name="mgrName" column="mgr_name"/>

        <!-- 映射1-1关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
        <one-to-one name="dept" class="com.cerr.hibernate.one2one.foreign.Department"
            property-ref="mgr"></one-to-one>
    </class>
</hibernate-mapping>

hibernate.cfg.xml中加入配置:

<mapping resource="com/cerr/hibernate/one2one/foreign/Manager.hbm.xml"/>
<mapping resource="com/cerr/hibernate/one2one/foreign/Department.hbm.xml"/>

测试类文件:

package com.cerr.hibernate.one2one.foreign;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;

public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }

    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

    @org.junit.Test
    public void test1(){
        Department department = session.get(Department.class,1);
        Manager manager = department.getMgr();
        System.out.println(manager.getMgrName());
    }

    @org.junit.Test
    public void test(){
        Department department = new Department();
        department.setDeptName("AA");
        Manager manager = new Manager();
        manager.setMgrName("aa");
        //设定关联关系
        manager.setDept(department);
        department.setMgr(manager);
        //保存操作
        session.save(manager);
        session.save(department);
    }
}

在查询有外键的实体对象时,使用的依旧是懒加载。其关联对象在需要使用时才初始化。
而在查询没有外键的实体对象时,发现使用的是左外连接查询,一并查询出其关联的对象并进行初始化。

基于主键映射的一对一关联关系

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

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

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

我们在上述的Demo中进行修改,首先因为都是一对一的关联关系,因此两个实体类是不需要修改的,要修改的是映射文件,修改后的映射文件如下:
Department.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.cerr.hibernate.one2one.primary.Manager" table="manager" schema="hibernate5" dynamic-update="true">
        <id name="mgrId" column="mgr_id">
            <generator class="native" />
        </id>
        <property name="mgrName" column="mgr_name"/>

        <!-- 映射1-1关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
        <one-to-one name="dept" class="com.cerr.hibernate.one2one.primary.Department"></one-to-one>
    </class>
</hibernate-mapping>

Manager.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.cerr.hibernate.one2one.primary.Manager" table="manager" schema="hibernate5" dynamic-update="true">
        <id name="mgrId" column="mgr_id">
            <generator class="native" />
        </id>
        <property name="mgrName" column="mgr_name"/>

        <!-- 映射1-1关联关系:在对应的数据表中已经有外键了,当前持久化类使用one-to-one进行映射-->
        <one-to-one name="dept" class="com.cerr.hibernate.one2one.primary.Department"></one-to-one>
    </class>
</hibernate-mapping>

映射单向多对多关联关系

单向n-n的关联必须使用连接表。

首先我们先建立两个实体类:

package com.cerr.hibernate.n2n;
import java.util.HashSet;
import java.util.Set;
public class Category {
    private Integer id;
    private String name;
    private Set<Item> items = new HashSet <>();
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Set < Item > getItems() {
        return items;
    }
    public void setItems(Set < Item > items) {
        this.items = items;
    }
}
package com.cerr.hibernate.n2n;
public class Item {
    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

对于映射文件:
与1-n映射类似,必须为set集合元素添加key子元素,指定categories_items表中参照categories表的外键为c_id,与1-n关联映射不同的是,建立n-n关联时,集合中的元素使用<many-to-many><many-to-many>子元素的class属性指定items集合中存放的是Item对象,<many-to-many>子元素的column属性指定categories_items表中参照items表的外键为i_id
Category.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n2n.Category" table="categories" schema="hibernate5" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native" />
        </id>
        <property name="name" column="name"/>
        <!-- table:指定中间表 -->
        <set name="items" table="categories_items">
            <key>
                <column name="c_id"/>
            </key>
            <!-- 使用many-to-many指定多对多的关联关系,column指定set集合中的持久化类在中间表的外键列的名称-->
            <many-to-many class="com.cerr.hibernate.n2n.Item" column="i_id"></many-to-many>
        </set>

    </class>
</hibernate-mapping>

Item.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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n2n.Item" table="items" schema="hibernate5" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native" />
        </id>
        <property name="name" column="name"/>
    </class>
</hibernate-mapping>

测试类:

package com.cerr.hibernate.n2n;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;
import java.util.Set;
public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;
    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }
    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }
    @org.junit.Test
    public void test1(){
        Category category = session.get(Category.class,3);
        System.out.println(category.getName());

        Set<Item> items = category.getItems();
        System.out.println(items.size());

    }
    @org.junit.Test
    public void test(){
        Category category = new Category();
        category.setName("AA");
        Category category1  =new Category();
        category1.setName("BB");
        Item item = new Item();
        Item item1 = new Item();
        item.setName("aa");
        item1.setName("bb");
        //设定关联关系
        category.getItems().add(item);
        category.getItems().add(item1);
        category1.getItems().add(item);
        category1.getItems().add(item1);
        //执行保存操作
        session.save(category);
        session.save(category1);
        session.save(item);
        session.save(item1);
    }
}

映射双向多对多关联关系

  • 双向n-n关联需要两端都使用集合属性,并且需要使用连接表。

  • 集合属性应增加key子元素用以映射外键列,集合元素里还应增加many-to-many子元素关联实体类。

  • 在双向n-n关联的两边都需指定连接表的表名及外键列的列名,两个集合元素的settable元素的值必须指定,而且必须相同,该值是连接表的表名。set元素的两个子元素:keymany-to-many都必须指定column属性,其中keymany-to-many分别指定本持久化类和关联类在连接表中的外键列名,因为两边的keymany-to-manycolumn属性交叉相同。
    例如一个边的set元素的keycolumn值为a,many-to-manycolumn为b;则另一边的set元素的keycolumn值为b,many-to-manycolumn值为a。

  • 对于双向n-n关联,必须把其中一端的inverse设置为true,否则两端都维护关联关系可能会造成主键冲突。

我们在上面实体类和映射文件的基础上改,需要在Item类中添加一个集合

package com.cerr.hibernate.n2n;

import java.util.HashSet;
import java.util.Set;

public class Item {
    private Integer id;
    private String name;
    private Set<Category> categories = new HashSet <>();

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Set < Category > getCategories() {
        return categories;
    }

    public void setCategories(Set < Category > categories) {
        this.categories = categories;
    }
}

再更改其映射文件Item.hbm.xml,需要添加<set>元素

<?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>
    <!--加入select-before-update="true" -->
    <class name="com.cerr.hibernate.n2n.Item" table="items" schema="hibernate5" dynamic-update="true">
        <id name="id" column="id">
            <generator class="native" />
        </id>
        <property name="name" column="name"/>
        <!-- 指定中间表 -->
        <set name="categories" table="categories_items" inverse="true">
            <key column="i_id"></key>
            <many-to-many class="com.cerr.hibernate.n2n.Category" column="c_id"></many-to-many>
        </set>
    </class>
</hibernate-mapping>

测试类的方法修改为如下:

@org.junit.Test
    public void test(){
        Category category = new Category();
        category.setName("AA");
        Category category1  =new Category();
        category1.setName("BB");
        Item item = new Item();
        Item item1 = new Item();
        item.setName("aa");
        item1.setName("bb");
        //设定关联关系
        category.getItems().add(item);
        category.getItems().add(item1);

        category1.getItems().add(item);
        category1.getItems().add(item1);
        item.getCategories().add(category);
        item.getCategories().add(category1);
        item1.getCategories().add(category);
        item1.getCategories().add(category1);
        //执行保存操作
        session.save(category);
        session.save(category1);
        session.save(item);
        session.save(item1);
    }

映射继承关系

对于面向对象的程序设计语言而言,继承和多态是两个最基本的概念,Hibernate的继承映射可以理解持久化类之间的继承关系。例如:人和学生之间的关系,学生继承了人,可以认为学生是一个特殊的人,如果对人进行查询,学生的实例也将被得到。

采用subclass元素的继承映射

  • 采用subclass的继承映射可以实现对于继承关系中父类和子类使用同一张表
  • 因为父类和子类的实例全部保存在同一个表中,因此需要在该表内增加一列,使用该列来区分每行记录到底是哪个类的实例,这个列被称为辨别者列。
  • 在这种映射策略下,使用subclass来映射子类,使用classsubclassdiscriminator-value属性指定辨别者列的值
  • 所有子类定义的字段都不能有非空约束。如果为那些字段添加非空约束,那么父类的实例在那些列其实并没有值,这将引起数据库完整性冲突,导致父类的实例无法保存到数据库中。

新建实体类PersonStudent类,Student类继承自Person类。

package com.cerr.hibernate.subclass;

public class Person {
    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

package com.cerr.hibernate.subclass;

public class Student extends Person{
    private String school;

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }
}

映射文件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 package="com.cerr.hibernate.subclass">
    <!--加入select-before-update="true" -->
    <class name="Person" table="persons" schema="hibernate5" discriminator-value="Person">
        <id name="id" column="id">
            <generator class="native" />
        </id>
        <!-- 配置辨别者列-->
        <discriminator column="type" type="java.lang.String"></discriminator>
        <property name="name" column="name"/>
        <property name="age" column="age"/>

        <!-- 映射子类Student,使用subclass进行映射-->
        <subclass name="Student" discriminator-value="Student">
            <property name="school" type="java.lang.String" column="school"></property>
        </subclass>

    </class>
</hibernate-mapping>

在Hibernate配置文件中添加:
<mapping resource="com/cerr/hibernate/subclass/Person.hbm.xml"/>

测试类:

package com.cerr.hibernate.subclass;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;

import java.util.List;

public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }

    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

    /**
     * 缺点:
     * 1.使用了辨别者列
     * 2.子类独有的字段不能添加非空约束
     * 3.若继承层次很深,则数据表的字段也会较多。
     */

    /**
     * 查询
     * 1.查询父类记录,只需要查询一张数据表
     * 2.对于子类记录,也只需要查询一张数据表
     */
    @org.junit.Test
    public void testQuery(){
        List<Person> personList = session.createQuery("FROM Person ").list();
        System.out.println(personList.size());

        List<Student> students = session.createQuery("FROM Student").list();
        System.out.println(students.size());
    }
    /**
     * 插入操作:
     * 1.对于子类对象只需把记录插入到一张数据表中
     * 2.辨别者列由Hibernate自动维护。
     */
    @org.junit.Test
    public void testSave(){
        Person person = new Person();
        person.setAge(11);
        person.setName("aa");
        session.save(person);

        Student student = new Student();
        student.setAge(12);
        student.setSchool("aa");
        student.setName("aaa");
        session.save(student);
    }
}

运行后新建了一个表,如下:


采用joined-subclass元素的继承映射

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

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

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

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

我们还是用上述的PersonStudent的例子,我们需要在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 package="com.cerr.hibernate.joined">

    <class name="Person" table="persons" >
        <id name="id" column="id">
            <generator class="native" />
        </id>

        <property name="name" column="name"/>
        <property name="age" column="age"/>
        <!-- 使用joined-subclass -->
        <joined-subclass name="Student" table="students">
            <key column="student_id"></key>
            <property name="school" type="java.lang.String" column="school"></property>
        </joined-subclass>
    </class>
</hibernate-mapping>

然后在配置文件中加上:
<mapping resource="com/cerr/hibernate/joined/Person.hbm.xml"/>
测试类还是使用上面的,运行后将会生成两张表,一张父类表,一张子类独有的属性的表,子类的数据为父类表的字段+子类表的字段。


对于插入操作,插入子类对象需要插两张表。
对于查询操作,查询父类记录时,做一个左外连接查询;查询子类记录,做一个内连接查询。

采用union-subclass元素的继承映射

  • 采用union-subclass元素可以实现将每一个实体对象映射到一个独立的表中
  • 子类增加的属性可以有非空约束,即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中。
  • 子类实例的数据仅保存在子类表中,而在父类表中没有任何记录。
  • 在这种映射策略下,子类表的字段会比父类表的映射字段要多,因为子类表的字段等于父类表的字段加上子类独有属性的总和。
  • 在这种映射策略下,既不需要使用鉴别者列,也无序使用key元素来映射共有主键
  • 使用union-subclass映射策略是不可使用identity的主键生成策略,因为同一类继承层次中所有实体类都需要使用同一个主键种子,即多个持久化实体对应的记录的主键应该是连续的,受此影响,也不该使用native主键生成策略,因为native会根据数据库来选择使用identitysequence

新建Person类和Student
Person类:

package com.cerr.hibernate.union.subclass;

public class Person {
    private Integer id;
    private String name;
    private Integer age;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

Student类:

package com.cerr.hibernate.union.subclass;

public class Student extends Person {
    private String school;

    public String getSchool() {
        return school;
    }

    public void setSchool(String school) {
        this.school = school;
    }
}

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 package="com.cerr.hibernate.union.subclass">

    <class name="Person" table="PERSONS"  >
        <id name="id" column="ID" type="java.lang.Integer">
            <generator class="increment" />
        </id>

        <property name="name" column="NAME"/>
        <property name="age" column="AGE"/>

        <union-subclass name="Student" table="STUDENTS">
            <property name="school" column="SCHOOL" type="java.lang.String"/>
        </union-subclass>
    </class>
</hibernate-mapping>

测试类:

package com.cerr.hibernate.union.subclass;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.After;
import org.junit.Before;

public class Test {
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    @Before
    public void init() throws Exception {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
        session = sessionFactory.openSession();
        transaction = session.beginTransaction();
    }

    @After
    public void destory() throws Exception {
        transaction.commit();
        session.close();
        sessionFactory.close();
    }

    /**
     * 优点:
     * 1.不需要使用辨别者列
     * 2.子类独有的字段能添加非空约束
     *
     * 缺点:
     * 1.子表中存在冗余的字段
     * 2.若更新父表的字段,则更新的效率较低。
     */

    /**
     * 查询
     * 1.查询父类记录,需把父表和子表记录汇总到一起再做查询,性能稍差。
     * 2.对于子类记录,也只需要查询一张数据表
     */
    @org.junit.Test
    public void testQuery(){
        List<Person> personList = session.createQuery("FROM Person ").list();
        System.out.println(personList.size());

        List<Student> students = session.createQuery("FROM Student").list();
        System.out.println(students.size());
    }
    /**
     * 插入操作:
     * 1.对于子类对象只需把记录插入到一张数据表中。
     */
    @org.junit.Test
    public void testSave(){
        Person person = new Person();
        person.setAge(11);
        person.setName("aa");
        session.save(person);

        Student student = new Student();
        student.setAge(12);
        student.setSchool("aa");
        student.setName("aaa");
        session.save(student);
    }
}

生成的表如下:



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章