关联关系

工程结构:

一. 单向多对一

Customer.java:

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

 Order.java:

public class Order {
private Integer orderId;
private String orderName;
private Customer customer; // 关联关系

 Customer.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.entities.n21.Customer" table="CUSTOMERS">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMER_NAME" />
</property>
</class>
</hibernate-mapping>

 Order.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.n21">
<class name="Order" table="ORDERS">
<id name="orderId" type="java.lang.Integer">
<column name="ORDER_ID" />
<generator class="native" />
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME" />
</property>

<!-- 映射Order到Customer的多对一关联关系: column用于指定ORDERS表的外键 -->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"/>
</class>
</hibernate-mapping>

 hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<!-- 连接数据库 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate5</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>

<!-- 数据库方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

<!-- 是否打印sql -->
<property name="hibernate.show_sql">true</property>

<!-- sql是否格式化 -->
<property name="hibernate.format_sql">true</property>

<!-- 设置数据库的隔离级别 -->
<property name="hibernate.connection.isolation">2</property>

<property name="hibernate.use_identifier_rollback">true</property>

<!-- 配置C3P0数据源 -->
<property name="hibernate.c3p0.max_size">10</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.acquire_increment">2</property>
<property name="hibernate.c3p0.idle_test_period">2000</property>
<property name="hibernate.c3p0.timeout">2000</property>
<property name="hibernate.c3p0.max_statements">10</property>
<property name="hibernate.jdbc.fetch_size">100</property>
<property name="hibernate.jdbc.batch_size">30</property>

<!-- 生成数据表的策略 -->
<property name="hibernate.hbm2ddl.auto">update</property>

<!-- 导入*.hbm.xml对象关系映射文件 -->
<mapping resource="com/atguigu/hibernate/entities/n21/Customer.hbm.xml"/>
<mapping resource="com/atguigu/hibernate/entities/n21/Order.hbm.xml"/>
</session-factory>
</hibernate-configuration>

 HibernateTest.java:

 package com.atguigu.hibernate.entities.n21;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class HibernateTest {

// 成员变量
private SessionFactory sessionFactory; // 是线程安全的
// 注意: 在实际开发中, Transaction和Session不能作为成员变量, 会引发并发问题
private Transaction transaction;
private Session session;

@Before
public void init() {
// 创建SessionFactory对象
Configuration configuration = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.buildServiceRegistry();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
// 创建Session对象
session = sessionFactory.openSession();
// 创建事务对象
transaction = session.beginTransaction();
}

@Test
public void testMany2One() {

}

@After
public void destroy() {
// 提交事务
transaction.commit();
// 关闭Session
session.close();
// 关闭sessionFactory
sessionFactory.close();
}
}

测试:

save():

@Test
public void testMany2One() {
Customer customer1 = new Customer();
customer1.setCustomerName("customer-1");

Order order1 = new Order();
order1.setOrderName("order-1");
Order order2 = new Order();
order2.setOrderName("order-2");

// 设定管理关系
order1.setCustomer(customer1);
order2.setCustomer(customer1);

// 保存
session.save(customer1);
session.save(order1);
session.save(order2);
}

数据库:

先插入Customer, 后插入Order, 后台打印3条INSERT语句

先插入Order, 后插入Customer, 后台打印3条INSERT语句, 2条UPDATE语句, 因为在插入Order的时候CUSTOMER_ID还没有办法确定

直到Customer插入之后CUSTOMER_ID才确定下来, 这时候为了维护关联关系, 就多发了2条UPDATE语句

  

get(): 

@Test
public void testMany2OneGet() {
// 只查询了Order, 不查询Customer
Order order = (Order) session.get(Order.class, 1);
System.out.println(order.getOrderName());

// 在使用到customer的时候才发送SELECT语句
Customer customer = order.getCustomer();
System.out.println(customer.getCustomerName());
}

update():

@Test
public void testMany2OneUpdate() {
Order order = (Order) session.get(Order.class, 1);
order.getCustomer().setCustomerName("007");
}

 delete(): 

因为设置了级联关系, 不能删除Customer对象, 因为删除了之后Order就丢失了外键, 可以删除Order

@Test
public void testMany2OneDelete() {
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}

-----------------------------------------------------

二. 双向一对多

save():

修改Customer.java:

因为是一(Customer)对多(Order), 所以要在Customer中添加private Set<Order> orders = new HashSet<>(); + getter/setter

修改Customer.hbm.xml:

<!-- 配置双向一对多关联关系: 因为orders是放在ORDERS表中的,
所以这里需要提供表名和外键名, 同时, 为了区分many-to-many, 还要指定类型 -->
<set name="orders" table="ORDERS">
  <key column="CUSTOMER_ID" />
  <one-to-many class="Order"/>
</set>

HibernateTest.java:

@Test
public void testOne2Many() {
Customer customer1 = new Customer();
customer1.setCustomerName("customer-1");

Order order1 = new Order();
order1.setOrderName("order-1");
Order order2 = new Order();
order2.setOrderName("order-2");

// 设定管理关系
order1.setCustomer(customer1);
order2.setCustomer(customer1);
customer1.getOrders().add(order1);
customer1.getOrders().add(order2);

// 保存, 先插入customer, 后插入order
session.save(customer1);
session.save(order1);
session.save(order2);
}

后台打印: 先插入Customer, 同时Customer中有Order的Set集合, orderId先暂时为null, 直到插入Order时才有orderId,

为了维护级联关系, 所以又多发了2条UPDATE语句

数据库:

先插入order, 后插入customer

session.save(order1);
session.save(order2);
session.save(customer1);

后台: 3条INSERT和4条UPDATE

 

数据库:

 总之: 为了提高效率, 应该先插入能确定的一端[1的一端], 不能确定的应该后插入

 <set> 元素的 inverse 属性

  在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系 在没有设置 inverse=true 的情况下,父子两边都维护父子关系 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多) 在 1-N 关系中,若将 1 方设为主控方 会额外多出 update 语句。 插入数据时无法同时插入外键列,因而无法为外键列添加非空约束.

   在Customer.hbm.xml中设置inverse属性为true, 维护关联关系的权利反转, 即把维护关联关系的任务交给Order这一方(多的一方)

<set name="orders" table="ORDERS" inverse="true">

 后台: 3条INSERT和2条UPDATE, 比之前少了2条UPDATE

 get():

@Test
public void testOne2ManyGet() {
// 只查customer, 对于多的一端使用延迟加载, 用的时候才获取
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer.getCustomerName());
}

update():

@Test
public void testOne2ManyUpdate() {
Customer customer = (Customer) session.get(Customer.class, 1);
// 取表中的某一行[因为Set是无序的]记录进行修改
customer.getOrders().iterator().next().setOrderName("XXXXX");
}

delete():

@Test
public void testOne2ManyDelete() {
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}

 也不能删除, 因为由外键约束, 可以设置级联删除, 如下:

<set name="orders" table="ORDERS" inverse="true" cascade="delete">

 测试: 已经删除了CUSTOMERS表中customerId为1的那一条记录, 同时, 其关联的ORDERS表中customerId为1的所有记录行也会跟着删除

解除customer和order的关系, 然后删除customer, 这时候order因为丢失了cutomerId外键将变为"孤儿", 相当于没有指引指向, 最后变为垃圾删除掉
准备数据:

修改cascade: <set name="orders" table="ORDERS" inverse="true" cascade="delete-orphan">

 修改测试:

@Test
public void testOne2ManyDelete() {
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}

 数据库:

总结: 在开发之中不建议使用cascade级联操作, 建议使用手工删除.

对order集合进行排序操作:

准备数据:

 

@Test
public void testOrderBy() {
Customer customer = (Customer) session.get(Customer.class, 1);
Set<Order> orders = customer.getOrders();
Iterator<Order> iterator = orders.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().getOrderId());
}
}

(1) 没有设置排序的Set集合打印:

 

(2) 设置排序的Set集合打印: <set name="orders" table="ORDERS" inverse="true" order-by="ORDER_ID DESC">

<set name="orders" table="ORDERS" inverse="true" order-by="ORDER_ID"

 

---------------------------------------------------------

三. 一对一关联关系

(1) 基于外键映射的一对一

工程结构:

Department.java:

public class Department {

private Integer deptId;

private String deptName;

private Manager mgr;

Manager.java:

public class Manager {

private Integer mgrId;

private String mgrName;

private Department dept;

Department.hbm.xml:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.atguigu.hibernate.entities.one2one">

    <class name="Department" table="DEPARTMENTS">

        <id name="deptId" type="java.lang.Integer">

            <column name="DEPT_ID" />

            <generator class="native" />

        </id>

        <property name="deptName" type="java.lang.String">

            <column name="DEPT_NAME" />

        </property>

<!-- 使用many-to-one来映射1-1的关联关系, 这样配置的意思: 多个部门对应一个经理,
然后将部门中的经理的id设置成unique唯一约束, 使得一个部门只能对应着一个经理,
同时不要忘记还要在经理中配置1-1的关联关系
-->

        <many-to-one name="mgr" column="MGR_ID" class="Manager" unique="true" />

    </class>

</hibernate-mapping>

Manager.hbm.xml:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <class name="com.atguigu.hibernate.entities.one2one.Manager" table="MANAGERS">

        <id name="mgrId" type="java.lang.Integer">

            <column name="MGR_ID" />

            <generator class="native" />

        </id>

        <property name="mgrName" type="java.lang.String">

            <column name="MGR_NAME" />

        </property>

        <!-- 配置1-1的关联关系 -->

        <one-to-one name="dept" class="com.atguigu.hibernate.entities.one2one.Department" />

    </class>

</hibernate-mapping>

hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-configuration PUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

 

<hibernate-configuration>

    <session-factory>

    <!-- 连接数据库 -->

    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate5</property>

    <property name="hibernate.connection.username">root</property>

    <property name="hibernate.connection.password">123456</property>

   

    <!-- 数据库方言 -->

    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

   

    <!-- 是否打印sql -->

    <property name="hibernate.show_sql">true</property>

   

    <!-- sql是否格式化 -->

<!--     <property name="hibernate.format_sql">true</property> -->

   

    <!-- 设置数据库的隔离级别 -->

    <property name="hibernate.connection.isolation">2</property>

   

    <property name="hibernate.use_identifier_rollback">true</property>

   

    <!-- 配置C3P0数据源 -->

    <property name="hibernate.c3p0.max_size">10</property>

    <property name="hibernate.c3p0.min_size">5</property>

    <property name="hibernate.c3p0.acquire_increment">2</property>

    <property name="hibernate.c3p0.idle_test_period">2000</property>

    <property name="hibernate.c3p0.timeout">2000</property>

    <property name="hibernate.c3p0.max_statements">10</property>

    <property name="hibernate.jdbc.fetch_size">100</property>

    <property name="hibernate.jdbc.batch_size">30</property>

   

    <!-- 生成数据表的策略 -->

    <property name="hibernate.hbm2ddl.auto">update</property>

   

    <!-- 导入*.hbm.xml对象关系映射文件 -->

    <mapping resource="com/atguigu/hibernate/entities/one2one/Department.hbm.xml"/>

    <mapping resource="com/atguigu/hibernate/entities/one2one/Manager.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

HibernateTest.java:

package com.atguigu.hibernate.entities.one2one;

 

import org.hibernate.Session;

import org.hibernate.SessionFactory;

import org.hibernate.Transaction;

import org.hibernate.cfg.Configuration;

import org.hibernate.service.ServiceRegistry;

import org.hibernate.service.ServiceRegistryBuilder;

import org.junit.After;

import org.junit.Before;

import org.junit.Test;

 

public class HibernateTest {

 

// 成员变量

private SessionFactory sessionFactory;// 是线程安全的

// 注意: 在实际开发中, Transaction和Session不能作为成员变量, 会引发并发问题

private Transaction transaction;

private Session session;

 

@Before

public void init() {

// 创建SessionFactory对象

Configuration configuration = new Configuration().configure();

ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()

.applySettings(configuration.getProperties())

.buildServiceRegistry();

sessionFactory = configuration.buildSessionFactory(serviceRegistry);

// 创建Session对象

session = sessionFactory.openSession();

// 创建事务对象

transaction = session.beginTransaction();

}

 

@Test

public void test() {

 

}

 

@After

public void destroy() {

// 提交事务

transaction.commit();

// 关闭Session

session.close();

// 关闭sessionFactory

sessionFactory.close();

}

}

测试: 执行test()方法

数据库:

 

----------------------------------以下是CRUD--------------------------------------

 save():

@Test
public void save() {
Department dept = new Department();
dept.setDeptName("DEPT-AA");
Manager mgr = new Manager();
mgr.setMgrName("MGR-AA");

dept.setMgr(mgr);
mgr.setDept(dept);

session.save(mgr);
session.save(dept);
}

get(): 同样也是懒加载, 只查询dept, 并不会查询mgr, 当需要mgr的时候才加载

 @Test

public void get() {
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept);
}

 ---------------

@Test
public void get() {
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getDeptName());

Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}

 后台sql:

Hibernate: select department0_.DEPT_ID as DEPT_ID1_0_0_, department0_.DEPT_NAME as DEPT_NAM2_0_0_, department0_.MGR_ID as MGR_ID3_0_0_ from DEPARTMENTS department0_ where department0_.DEPT_ID=?
DEPT-AA
Hibernate: select manager0_.MGR_ID as MGR_ID1_1_1_, manager0_.MGR_NAME as MGR_NAME2_1_1_, department1_.DEPT_ID as DEPT_ID1_0_0_, department1_.DEPT_NAME as DEPT_NAM2_0_0_, department1_.MGR_ID as MGR_ID3_0_0_ from MANAGERS manager0_ left outer join DEPARTMENTS department1_ on manager0_.MGR_ID=department1_.DEPT_ID where manager0_.MGR_ID=?
MGR-AA

 左外连接的连接条件不对, 应该是manager0_.MGR_ID=department1_.MGR_ID, 这是因为在之前的Manager.hbm.xml中少映射了一个属性, 修改:

<!-- 配置1-1的关联关系 -->
<one-to-one name="dept" property-ref="mgr"
class="com.atguigu.hibernate.entities.one2one.Department" />

 后台sql: 正确, property-ref="mgr"表示在dept中使用mgr的对应的那个字段对应的那一列[即MGR_ID]作为外键关联

 Hibernate: select department0_.DEPT_ID as DEPT_ID1_0_0_, department0_.DEPT_NAME as DEPT_NAM2_0_0_, department0_.MGR_ID as MGR_ID3_0_0_ from DEPARTMENTS department0_ where department0_.DEPT_ID=?

DEPT-AA
Hibernate: select manager0_.MGR_ID as MGR_ID1_1_1_, manager0_.MGR_NAME as MGR_NAME2_1_1_, department1_.DEPT_ID as DEPT_ID1_0_0_, department1_.DEPT_NAME as DEPT_NAM2_0_0_, department1_.MGR_ID as MGR_ID3_0_0_ from MANAGERS manager0_ left outer join DEPARTMENTS department1_ on manager0_.MGR_ID=department1_.MGR_ID where manager0_.MGR_ID=?
Hibernate: select department0_.DEPT_ID as DEPT_ID1_0_0_, department0_.DEPT_NAME as DEPT_NAM2_0_0_, department0_.MGR_ID as MGR_ID3_0_0_ from DEPARTMENTS department0_ where department0_.MGR_ID=?
MGR-AA

 ---------------------------------------

@Test
public void get2() {
Manager mgr = (Manager) session.get(Manager.class, 1);
System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getDeptName());
}

后台:

Hibernate: select department0_.DEPT_ID as DEPT_ID1_0_0_, department0_.DEPT_NAME as DEPT_NAM2_0_0_, department0_.MGR_ID as MGR_ID3_0_0_ from DEPARTMENTS department0_ where department0_.DEPT_ID=?
DEPT-AA
Hibernate: select manager0_.MGR_ID as MGR_ID1_1_1_, manager0_.MGR_NAME as MGR_NAME2_1_1_, department1_.DEPT_ID as DEPT_ID1_0_0_, department1_.DEPT_NAME as DEPT_NAM2_0_0_, department1_.MGR_ID as MGR_ID3_0_0_ from MANAGERS manager0_ left outer join DEPARTMENTS department1_ on manager0_.MGR_ID=department1_.MGR_ID where manager0_.MGR_ID=?
Hibernate: select department0_.DEPT_ID as DEPT_ID1_0_0_, department0_.DEPT_NAME as DEPT_NAM2_0_0_, department0_.MGR_ID as MGR_ID3_0_0_ from DEPARTMENTS department0_ where department0_.MGR_ID=?
MGR-AA

  这时候把关联的dept也查出来了, 因为在MANAGERS表中只有MGR_ID和MGR_NAME两个字段, 如果已经查出来了mgr, 这时候就应该把dept的信息也查出来

不然无法使用

(2) 基于主键映射的一对一[两个配置都是one-to-one], 使用dept的外键生成mgr的主键

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

  

    采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;另一端增加one-to-one元素映射关联属性。 constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键

   

Department.hbm.xml:

 <?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.one2one">
<class name="Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<generator class="foreign">
  <param name="property">mgr</param>
</generator>
</id>
<property name="deptName" type="java.lang.String">
<column name="DEPT_NAME" />
</property>
<one-to-one name="mgr" constrained="true"
class="com.atguigu.hibernate.entities.one2one.Manager" />
</class>
</hibernate-mapping>

 Manager.hbm.xml:

 <?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.entities.one2one.Manager" table="MANAGERS">
<id name="mgrId" type="java.lang.Integer">
<column name="MGR_ID" />
<generator class="native" />
</id>
<property name="mgrName" type="java.lang.String">
<column name="MGR_NAME" />
</property>
<!-- 配置1-1的关联关系 -->
<one-to-one name="dept"
class="com.atguigu.hibernate.entities.one2one.Department" />
</class>
</hibernate-mapping>

 -----------------

@Test
public void save() {
Department dept = new Department();
dept.setDeptName("DEPT-AA");
Manager mgr = new Manager();
mgr.setMgrName("MGR-AA");

dept.setMgr(mgr);
mgr.setDept(dept);

session.save(mgr);
session.save(dept);
}

 数据库:

这种情况下, 先插入哪一个都不会有update语句, 因为MANAGERS的主键不能为空, 所以必须先插入MANAGERS, 不管先插入哪一个, sql都是:

Hibernate: insert into MANAGERS (MGR_NAME) values (?)
Hibernate: insert into DEPARTMENTS (DEPT_NAME, DEPT_ID) values (?, ?)

 先插入MANAGERS, 后插入DEPARTMENTS 

 ---------------------------------------------------------

四. 多对多关联关系

 多对多关联关系必须使用中间表

  n-n 的关联必须使用连接表 与 1-n 映射类似,必须为 set 集合元素添加 key 子元素,指定 CATEGORIES_ITEMS 表中参照 CATEGORIES 表的外键为 CATEGORIY_ID. 与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用 many-to-many. many-to-many 子元素的 class 属性指定 items 集合中存放的是 Item 对象, column 属性指定 CATEGORIES_ITEMS 表中参照 ITEMS 表的外键为 ITEM_ID

  双向 n-n 关联需要两端都使用集合属性 双向n-n关联必须使用连接表 集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类 在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名. 两个集合元素 set 的 table 元素的值必须指定,而且必须相同。set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a. 对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突.

-------------------------------------------

(1) 单向n-n

 Category.java:

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

 Item.java:

 public class Item {

private Integer id;
private String name;

 Category.hbm.xml:

 <?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.many2many">
<class name="Category" table="CATEGORYS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<set name="items" table="CATEGORY_ITEM">
<key>
<column name="C_ID" />
</key>
<many-to-many class="Item" column="I_ID" />
</set>
</class>
</hibernate-mapping>

 Item.hbm.xml:

 <?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.atguigu.hibernate.entities.many2many.Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
</class>
</hibernate-mapping>

 HibernateTest.java:

@Test
public void testSave() {

}

 测试:

save():

@Test
public void testSave() {
Category category1 = new Category();
category1.setName("C-AA");
Category category2 = new Category();
category2.setName("C-BB");

Item item1 = new Item();
item1.setName("I-AA");
Item item2 = new Item();
item2.setName("I-BB");

// 设定关联关系
category1.getItems().add(item1);
category1.getItems().add(item2);
category2.getItems().add(item1);
category2.getItems().add(item2);

session.save(category1);
session.save(category2);
session.save(item1);
session.save(item2);
}

     

get():  支持懒加载

 @Test

public void testGet() {
Category category = (Category) session.get(Category.class, 1);
System.out.println(category.getName());
}

 

@Test
public void testGet() {
Category category = (Category) session.get(Category.class, 1);
System.out.println(category.getName());

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

 

(2) 双向多对多: 使用同一张中间表, 外键交叉相同

 Item.java:

 public class Item {

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

 Item.hbm.xml:

 

HibernateTest.java:

 测试: 报错, 两边都维护关联关系, 主键重复

 

需要在其中一方设置维护关联关系:

<set name="categories" table="CATEGORY_ITEM" inverse="true">

 测试:

 

     

----------------------------------------------------------

. 映射继承关系

 

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

Hibernate支持三种继承映射策略:

1. 使用 subclass 进行映射:将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。

Person.java:

public class Person {

private Integer id;
private String name;
private Integer age;

Student.java:

public class Student extends Person {
private String school;

Person.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.many2many">
<class name="Person" table="PERSONS" discriminator-value="PERSON">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<!-- 映射辨别者列 -->
<discriminator column="TYPE" type="string" />
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="java.lang.Integer">
<column name="AGE" />
</property>
<!-- 映射继承关系: subClass -->
<subclass name="Student" discriminator-value="STUDENT">
<property name="school" type="string" column="SCHOOL" />
</subclass>
</class>
</hibernate-mapping>

----------

@Test
public void testGet() {

}

save():

@Test
public void testGet() {
Person person = new Person();
person.setName("P-AA");
person.setAge(11);

session.save(person);

Student student = new Student();
student.setName("S-AA");
student.setAge(22);
student.setSchool("ATGUIGU");

session.save(student);
}

 数据库:

后台:

query(): 多态查询, 只需要查一张表

@Test
public void testQuery() {
List<Person> persons = session.createQuery("FROM Person").list();
System.out.println(persons.size());
}

List<Student> students = session.createQuery("FROM Student").list();
System.out.println(students.size());

 也只需要查询一张表:

这种映射的缺点:

 

2. 使用 joined-subclass 进行映射: 对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。

 

Person.hbm.xml:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.atguigu.hibernate.entities.many2many">
<class name="Person" table="PERSONS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="java.lang.Integer">
<column name="AGE" />
</property>
<!-- 映射继承关系: joinClass -->
<joined-subclass name="Student" table="STUDENTS">
<key column="STUDENT_ID"></key>
<property name="school" type="string" column="SCHOOL" />
</joined-subclass>
</class>
</hibernate-mapping>

--------

@Test
public void testSave() {
Person person = new Person();
person.setName("P-AA");
person.setAge(11);

session.save(person);

Student student = new Student();
student.setName("S-AA");
student.setAge(22);
student.setSchool("ATGUIGU");

session.save(student);
}

   

STUDENTS表的外键参照PERSONS表的主键, 对于子类对象至少插入两张数据表, 性能比subclass差

查询也需要查两张表, 性能也比subclass差

但是生成了两张表, 没有冗余字段

 

3. 使用 union-subclass 进行映射:域模型中的每个类映射到一个表,通过关系数据模型中的外键来描述表之间的继承关系。这也就相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。

 

Person.hbm.xml

 

------------ 这种方式需要使用hilo的主键生成策略

只有两条INSERT, 插入性能不错!

查询性能: 查子类不错, 查父类有子查询, 性能偏弱, 无需使用辨别者列, 子类独有的字段能添加非空约束, 有冗余字段

更新性能: 更新父表的字段的时候, 比较麻烦, 效率较低

根据项目需要选择哪一种方法, 但是推荐使用union-subclass或者joined-subclass

 

posted @ 2017-03-14 21:15  半生戎马,共话桑麻、  阅读(281)  评论(0)    收藏  举报
levels of contents