关联关系
工程结构:

一. 单向多对一
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


浙公网安备 33010602011771号