一对多关联关系
在 UML 中, 关联是有方向的.
以 Customer 和 Order 为例: 一个用户能发出多个订单, 而一个订单只能属于一个客户. 从 Order 到 Customer 的关联是多对一关联; 而从 Customer 到 Order 是一对多关联
单向关联
双向关联
单向 n-1
单向 n-1 关联只需从 n 的一端可以访问 1 的一端
域模型: 从 Order 到 Customer 的多对一单向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中无需定义存放 Order 对象的集合属性
关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
显然无法直接用 property 映射 customer 属性
Hibernate 使用 元素来映射多对一关联关系
many-to-one节点属性
- 示例:描述多对一的关系
- 编写Customer类,描述顾客,表示1的一端
public class Customer {
private Integer id;
private String name;
//getter和setter方法
//toString方法
}
- 配置Customer.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.n21" >
<class name="Customer" table="CUSTOMER">
<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>
- 编写Orders程序类,该类描述多的一方,在类属性中需要有一个Customer类的引用
public class Orders {
private Integer oid;
private String orderName;
private Customer customer;
//getter和setter方法
//toString方法
}
- 编写 Order.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.n21" >
<class name="mao.shu.vo.n21.Orders" table="ORDERS">
<id name="oid" type="java.lang.Integer">
<column name="O_ID"/>
<generator class="native"/>
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME"/>
</property>
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
</class>
</hibernate-mapping>
- 在设置字段的时候,需要注意字段名称不要与数据库中的关键字重复
- 在hibernate.cfg.xml文件中配置这两个映射文件
<mapping resource="mao/shu/vo/Customer.hbm.xml"/>
<mapping resource="mao/shu/vo/Order.hbm.xml"/>
- 测试多对一的增删改查方法
- 保存
@Test
public void testSave(){
Customer customer = new Customer();
customer.setCustomerName("xiemaoshu");
Orders order = new Orders();
order.setOrderName("cc");
order.setCustomer(customer);
this.session.save(customer);
this.session.save(order);
}
- 正常情况
- 如果先保存order ,在插入customer 的一段会多执行一些update语句,这是因为如果先插入 多的一段的时候,无法确定customer 的一段的外键值,所以只有当customer 的一段插入之后才能够获取到一的一端的id值,然后才能够更新n的一端的id值
- 当先保存order时多执行了一条更新语句
- 查询
@Test
public void testSelect(){
Orders orders = (Orders) this.session.get(Orders.class,1);
Customer customer = (Customer) this.session.get(Customer.class,orders.getCustomer().getCustomerId());
System.out.println(customer);
}
-
默认情况下不会查询关联一端的对象
-
在需要使用到关联的对象时,才发送对应的sql语句.
-
在查询时,如果session被关闭了,则无法加载关联对象,可能会出现懒加载异常.
-
在查询时,默认情况下 关联的对象为代理对象,还未完全加载.
-
更新方法
@Test
public void testUpdate(){
Orders orders = (Orders) this.session.get(Orders.class,1);
// this.session.close();
orders.setOrderName("测试修改");
this.session.update(orders);
}
- 删除方法
@Test
public void testDelete(){
Orders orders = new Orders();
orders.setOid(1);
this.session.delete(orders);
}
- 在不设定级联关系的情况下,不能够直接删除被关联的数据表中的数据,也就是Customer表,因为此时Order表中的有一个 CUSTOMER_ID 的字段与Customer表中的主键成外键关联关系.
- 所以只能够先删除 "Orders"表中的数据再删除 Customer表中的数据
双向一对多关系映射
- 一个客户可以拥有多个订单,可以在描述客户端java类之中使用一个Set集合来描述订单的关系.
- 这个Set集合需要先进行初始化,否则Hibernate在映射关系的时候可能会出现空指针异常,
- 域模型:从 Order 到 Customer 的多对一双向关联需要在Order 类中定义一个 Customer 属性, 而在 Customer 类中需定义存放 Order 对象的集合属性
- 关系数据模型:ORDERS 表中的 CUSTOMER_ID 参照 CUSTOMER 表的主键
- 示例:创建Customer程序类
public class Customer {
private Integer customerId;
private String customerName;
private Set<Orders> ordersSet = new HashSet<Orders>();
//getter和setter方法
//toString()方法
}
- 修改Customer.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.bothway" >
<class name="Customer" table="CUSTOMER">
<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>
<!--映射一对多的关系,
set标签描述一对多的关系,
set标签的name表示一对多的属性
set标签的table描述数据库中关联的表名称
key标签描述外键关联的字段
one-to-many标签描述关联的类-->
<set name="ordersSet">
<key column="CUSTOMER_ID"></key>
<one-to-many class="Orders"/>
</set>
</class>
</hibernate-mapping>
- 创建Orders程序类
public class Orders {
private Integer oid;
private String orderName;
private Customer customer;
//setter和getter()方法
//toString()方法
}
- Orders.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.bothway" >
<class name="Orders" table="ORDERS">
<id name="oid" type="java.lang.Integer">
<column name="O_ID"/>
<generator class="native"/>
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME"/>
</property>
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
</class>
</hibernate-mapping>
- 在hibernate.cfg.xml文件之中定义两个映射文件
<mapping resource="mao/shu/vo/bothway/Customer.hbm.xml"/>
<mapping resource="mao/shu/vo/bothway/Orders.hbm.xml"/>
- 测试保存操作
@Test
public void testSave(){
Customer customer = new Customer();
customer.setCustomerName("谢茂树");
Orders orders1 = new Orders();
orders1.setOrderName("kfc");
orders1.setCustomer(customer);
Orders orders2 = new Orders();
orders2.setOrderName("MCD");
orders2.setCustomer(customer);
//进行保存操作
this.session.save(customer);
this.session.save(orders1);
this.session.save(orders2);
}
- 通过执行结果可以看到,一共执行了三条"insert"语句.
- 如果先保存 Orders对象,在保存Customer对象则会发生什么?
//进行保存操作
this.session.save(orders1);
this.session.save(orders2);
this.session.save(customer);
-
通过结果可以发现如果先保存Orders对象再保存Customer对象,则会多执行两条update语句,这是因为在保存ORDER数据时,由于与之关联的Customer表中的数据还未保存,所以还没有关联的外键id,所以只有当Customer的数据保存之后需要再次更新外键id字段.
-
测试修改操作
@Test
public void testUpdate(){
Customer customer = (Customer) session.get(Customer.class,1);
System.out.println(customer);
System.out.println(customer.getOrdersSet());
customer.getOrdersSet().iterator().next().setOrderName("测试");
}
Set标签的三个属性
- inverse
- <set> 标签的 inverse 属性
- 在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
- 在没有设置 inverse=true 的情况下,父子两边都维护父子
关系- 在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
- 在 1-N 关系中,若将 1 方设为主控方
- 会额外多出 update 语句。
- 插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
- 修改Customer.hbm.xml文件,将Set集合设置为主动方
<set name="ordersSet" table="ORDERS" inverse="true">
- cascade:设置级联操作
- 开发的时候不建议设定该属性.建议使用手工的方式来处理.
- 在对象 – 关系映射文件中, 用于映射持久化类之间关联关系的元素, , 和 都有一个 cascade 属性, 它用于指定如何操纵与当前对象关联的其他对象.
- 示例:修改Customer.hbm.xml文件,设置Set集合的级联操作
- 设置级联更新操作
<set name="ordersSet" table="ORDERS" inverse="true" cascade="delete">
- 测试删除操作
@Test
public void testDelete(){
Customer customer = (Customer) session.get(Customer.class,1);
this.session.delete(customer);
}
- 此时只删除了一条customer数据,但却将order表中的另外两条数据也删除了.
- order-by :在数据库中对集合排序
<set> 元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序
order-by 属性中还可以加入 SQL 函数
- 示例:设置order-by
<set name="ordersSet" table="ORDERS" inverse="true" order-by="ORDER_NAME">
- 测试查询操作
@Test
public void testSelect(){
Customer customer = (Customer) session.get(Customer.class,4);
Orders orders2 = new Orders();
orders2.setOrderName("a");
Orders orders3 = new Orders();
orders3.setOrderName("b");
orders2.setCustomer(customer);
orders3.setCustomer(customer);
this.session.save(customer);
this.session.save(orders2);
this.session.save(orders3);
Set<Orders> set= customer.getOrdersSet();
System.out.println(set);
}
- 输出的结果,最后输出的集合结果,是按照orderName的值进行排序的
映射一对一关系
- 域模型
- 关系数据模型
- 按照外键映射
- 按照主键映射:
-
基于外键映射的 1-1
-
对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键一端,增加many-to-one元素。为many-to-one元素增加unique=“true” 属性使其不能够重复,来表示为1-1关联
-
另一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
-
示例:定义Department类
public class Department {
private Integer deptno;
private String dname;
private Manager manager;
//getter和setter方法
//toString方法
}
- 配置Department.hbm.xml映射文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.one2one" >
<class name="Department" table="DEPT">
<id name="deptno" type="java.lang.Integer">
<column name="DEPTNO"/>
<generator class="native"/>
</id>
<property name="dname" type="java.lang.String">
<column name="DNAME"/>
</property>
<!--描述外键关联的关系字段
name表示属性名称
class=外键字段所属的java类
column=外键关联字段名称-->
<many-to-one name="manager" class="Manager" column="MANAGER_ID"
unique="true" />
</class>
</hibernate-mapping>
- 编写Manager程序类
public class Manager {
private Integer mid;
private String manName;
private Integer age;
private Department dept;
//getter和setter方法
//toString方法
}
- 编写Manager程序类的映射文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.one2one" >
<class name="Manager" table="MANAGER">
<id name="mid" type="java.lang.Integer">
<column name="MID"/>
<generator class="native"/>
</id>
<property name="manName" type="java.lang.String">
<column name="MAN_NAME"/>
</property>
<property name="age" type="java.lang.Integer">
<column name="MAN_AGE"/>
</property>
<!--描述一对一关系
使用name描述属性名称
使用class描述属性所属的类型
-->
<one-to-one name="dept" class="Department" />
</class>
</hibernate-mapping>
- 在hibernate.cfg.xm文件中配置映射文件
<mapping resource="mao/shu/vo/one2one/Department.hbm.xml"/>
<mapping resource="mao/shu/vo/one2one/Manager.hbm.xml"/>
- 测试添加
@Test
public void testAdd(){
Manager manager = new Manager();
manager.setAge(22);
manager.setManName("xiemaoshu");
Department department = new Department();
department.setDname("开发部");
department.setManager(manager);
manager.setDept(department);
this.session.save(manager);
this.session.save(department);
}
- 测试查询
@Test
public void testGet(){
/*以manager表查询department表*/
Manager manager = (Manager) this.session.get(Manager.class,1);
System.out.println(manager.getAge());
System.out.println(manager.getManName());
System.out.println(manager.getMid());
System.out.println(manager.getDept().getClass());
//以department表查询manager表
Department department = (Department) this.session.get(Department.class,1);
System.out.println(department.getDeptno());
System.out.println(department.getDname());
System.out.println(department.getManager().getManName());
}
- 在查询结果中,可以发现在通过department表查询manager表中的时候,使用了做外链接的方式,但是做外链接的查询方式为"manager0_.MID=department1_.DEPTNO",这显然是不正确的,
- 这是由于在配置Manager.hbm.xml文件时,少配置了一样属性
- 修改Manager.hbm.xml,修改one-to-one标签,添加property-ref属性
- property-ref属性的作用为:指定关联类的属性名,这个属性将会和本类的主键相对应。如果没有指定,会使用对方关联类的主键。
- 此时Department类中的主键设置为"deptno"如果不设置"property-ref"的属性,就会使用"deptno"于Manager的主键所对应,所以就会出现"manager0_.MID=department1_.DEPTNO"的情况
<one-to-one name="dept" class="Department" property-ref="manager"/>
- 修改之后,比对正常
- 如果使用错误的关联类属性与本类主键对应(如:manager.id = department.id),这就会导致查询出来的数据错误
一对一基于主键关联
- 基于主键的映射策略:指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. <param> 子元素指定使用当前持久化类的哪个属性作为 “对方”的主键
- 采用foreign主键生成器策略的一端增加 one-to-one 元素映射关联属性,其one-to-one属性还应增加 constrained=“true” 属性;另一端增加one-to-one元素映射关联属性。
constrained(约束):指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(“对方”)所对应的数据库表主键
- 编写Department程序类
package mao.shu.vo.one2one.primary;
public class Department {
private Integer deptno;
private String dname;
private Manager manager;
//getter和setter方法
}
- Department.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.one2one.primary" >
<class name="Department" table="DEPT">
<id name="deptno" type="java.lang.Integer">
<column name="DEPTNO"/>
<!--根据关联类生成主键-->
<generator class="foreign">
<!--关联类的属性-->
<param name="property">manager</param>
</generator>
</id>
<property name="dname" type="java.lang.String">
<column name="DNAME"/>
</property>
<!--描述一对一关系-->
<one-to-one name="manager" class="Manager" constrained="true"/>
</class>
</hibernate-mapping>
- 编写Manager程序类
public class Manager {
private Integer mid;
private String manName;
private Integer age;
private Department dept;
//getter和setter方法
}
- Manager.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.one2one.primary" >
<class name="Manager" table="MANAGER">
<id name="mid" type="java.lang.Integer">
<column name="MID"/>
<generator class="native"/>
</id>
<property name="manName" type="java.lang.String">
<column name="MAN_NAME"/>
</property>
<property name="age" type="java.lang.Integer">
<column name="MAN_AGE"/>
</property>
<!--描述一对一关系
使用name描述属性名称
使用class描述属性所属的类型
-->
<one-to-one name="dept" class="Department" />
</class>
</hibernate-mapping>
- 在hibernate.cfg.xml文件中配置映射文件
<mapping resource="mao/shu/vo/one2one/primary/Manager.hbm.xml"/>
<mapping resource="mao/shu/vo/one2one/primary/Department.hbm.xml"/>
- 测试增加程序
@Test
public void testAdd(){
Manager manager = new Manager();
manager.setManName("谢茂树");
manager.setAge(22);
Department department = new Department();
department.setDname("开发部");
department.setManager(manager);
manager.setDept(department);
this.session.save(manager);
this.session.save(department);
}
- 测试查询
@Test
public void testGet(){
Manager manager = (Manager) this.session.get(Manager.class,163840);
System.out.println(manager.getManName());
System.out.println(manager.getAge());
System.out.println(manager.getMid());
System.out.println(manager.getDept().getDeptno());
}
- 运行结果
映射多对多关联关系
- 单向 n-n
- 域模型:
- 关系数据模型
实现多对多关系需要有一个中间表.
- 编写Category程序类
public class Item {
private Integer cid;
private String name;
private Set<Item> itemSet = new HashSet<Item>();
//setter和getter()方法
}
- 编写Category映射文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.manyToMany" >
<class name="Category" table="CATEGORY">
<id name="cid" type="java.lang.Integer">
<column name="C_ID"/>
<generator class="native"/>
</id>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
<!--使用集合描述items集合属性
使用name描述属性名称
使用table表示参照的表名称-->
<set name="itemSet" table="CATEGORY_ITEM">
<key>
<!--在CATEGORY_ITEM表中映射的字段为"CID"-->
<column name="C_ID"/>
</key>
<!--使用many-to-many描述多对多关系
使用class描述集合中对象所属的类型,
使用column描述映射到CATEGORY_ITEM数据表中的字段名称 -->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
- 编写Item程序类
public class Item {
private Integer iId;
private String name;
private Double price;
//getter和setter方法
}
- 编写Item.hbm.xml文件
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!--定义对象关系映射,package设置类所在的包名称-->
<hibernate-mapping package="mao.shu.vo.manyToMany" >
<class name="Item" table="Item">
<id name="iId" type="java.lang.Integer">
<column name="I_ID"/>
<generator class="native"/>
</id>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
<property name="price" type="java.lang.Double">
<column name="PRICE"/>
</property>
</class>
</hibernate-mapping>
- 在hibernate.cfg.xml文件之中配置映射路径
<mapping resource="mao/shu/vo/manyToMany/Category.hbm.xml"/>
<mapping resource="mao/shu/vo/manyToMany/Item.hbm.xml"/>
- 编写测试程序
@Test
public void testAdd(){
Item item1 = new Item();
item1.setName("衣服");
item1.setPrice(12340.2);
Item item2 = new Item();
item2.setName("iphone5");
item2.setPrice(456.123);
Item item3 = new Item();
item3.setName("照相机");
item3.setPrice(50000.12);
Category category = new Category();
category.setName("分类1");
category.getItemSet().add(item1);
category.getItemSet().add(item2);
Category category2 = new Category();
category2.setName("分类2");
category2.getItemSet().add(item3);
category2.getItemSet().add(item2);
Category category3 = new Category();
category3.setName("分类3");
category3.getItemSet().add(item1);
category3.getItemSet().add(item2);
category3.getItemSet().add(item3);
this.session.save(item1);
this.session.save(item2);
this.session.save(item3);
this.session.save(category);
this.session.save(category2);
this.session.save(category3);
}
- 输出结果
- 数据库中存储的数据
- category表
2. category_item表
3. item表
多对多双向关联
- 域模型
双向 n-n 关联需要两端都使用集合属性
双向n-n关联必须使用连接表
集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类
对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突.
- 在上述的代码基础上,在Item程序类中添加一个Set<Category>集合属性
private Set<Category> categorySet = new HashSet<Category>();
public Set<Category> getCategorySet() {
return categorySet;
}
public void setCategorySet(Set<Category> categorySet) {
this.categorySet = categorySet;
}
- 修改Item.hbm.xml文件,添加集合的多对多关系
<set name="categorySet" table="category_item" inverse="true">
<key>
<column name="C_ID"/>
</key>
<many-to-many class="Category" column="C_ID" ></many-to-many>
</set>
- 测试代码
@Test
public void testAdd(){
Item item1 = new Item();
item1.setName("衣服");
item1.setPrice(12340.2);
Item item2 = new Item();
item2.setName("iphone5");
item2.setPrice(456.123);
Item item3 = new Item();
item3.setName("照相机");
item3.setPrice(50000.12);
Category category = new Category();
category.setName("分类1");
category.getItemSet().add(item1);
category.getItemSet().add(item2);
Category category2 = new Category();
category2.setName("分类2");
category2.getItemSet().add(item3);
category2.getItemSet().add(item2);
Category category3 = new Category();
category3.setName("分类3");
category3.getItemSet().add(item1);
category3.getItemSet().add(item2);
category3.getItemSet().add(item3);
this.session.save(item1);
this.session.save(item2);
this.session.save(item3);
this.session.save(category);
this.session.save(category2);
this.session.save(category3);
}
- 添加成功