1. 映射关联关系
1.1映射的种类
以客户(Customer)和订单(Order)为例子。
客户 订单
1-1 一对一单向
1-1 一对一双向
1-n 一对多单向
n-1 多对一单向
1-n 一对多双向[和多对一双向一样]
n-m 多对多单向[可以分解为一对多]
n-m 多对多双向
1.2映射多对一[单向]关联关系
属于n-1单向模式。即多个订单订单单向关联客户。建表时,订单表要引用客户表的id作为外键。
理解例子中的多对一单向关联关系:
多个订单[多方]对应一个客户[一方],可以从订单自身获取该订单的客户信息(即订单知道自己属于哪个客户),但从客户上获取不到自身的订单信息(即客户不知道自己有哪些订单)。
实体类:
publicclass Customer {
//客户[一方],多对一单向,找不到订单
private Integer id; //ID
private String name;//客户名字
}
publicclass Order {
//订单[多方],多对一单向,可以找到客户
private Integer id; //ID
private String oderNum;//订单编号
private Customer customer;//客户
}
映射文件:
一方:
<class name="Customer"table="CUSTOMERS">
<id name="id"column="ID" type="integer">
<generator class="native"/>
</id>
<property name="name"column="NAME" type="string"/>
</class>
多方:
<class name="Order"table="ORDERS">
<id name="id"column="ID" type="integer">
<generator class="native"/>
</id>
<property name="orderNum"column="ORDERNUM" type="string"/>
<!-- 多对一映射
name:多方域对象customer属性名
class:多方域对象属性customer的类型
column:多方域对象对应的表orders的外键 -->
<many-to-one name="customer"class="Customer" column="CID"/>
</class>
保存客户和订单:
Customerc1 = new Customer("张三");
Ordero1 = new Order("N001");
Ordero2 = new Order("N002");
//设置多对一单向关联
o1.setCustomer(c1);
o2.setCustomer(c1);
/*
* 先保存订单再保存客户,
* 结果:执行5条sql语句,3条insert,2条update
session.save(o1);
session.save(o2);
session.save(c1);*/
/* 先保存客户再保存订单,
* 结果:只执行3条insert语句
* 结论:保存多对一单向关联对象时,先保存一方再保存多方
* 效率会高点。
*/
session.save(c1);
session.save(o1);
session.save(o2);
查询订单:
/*
* 当订单单向关联客户时,查询订单时
* 会将订单关联的客户一起查询出来
* 即o1.getCustomer()不为空
*/
Ordero1 =(Order) session.get(Order.class, 1); //查询id为1的订单
保存订单时级联保存关联的客户:
Customerc1 = new Customer("张三");
Ordero1 = new Order("N001");
Ordero2 = new Order("N002");
//设置多对一单向关联
o1.setCustomer(c1);
o2.setCustomer(c1);
/*
*保存订单时级联保存客户
* 需在Order.hbm.xml中的many-to-one节点配置
* cascade="save-update"属性,如果没有设置该属性
* 又没有手动执行session.save(c1)则出错(TransientObjectException)。因为订单
* 和客户是多对一单向关联关系,订单表引用了客户表的
* id作为外键,没有执行session.save(c1)即客户表没有记录
* 此时订单是保存不了的。
*/
session.save(o1);
session.save(o2);
<!-- 多对一映射
name:多方域对象customer属性名
class:多方域对象属性customer的类型
column:多方域对象对应的表orders的外键
cascade:多方域对象所关联的一切对象都会同步保存和更新-->
<many-to-one name="customer"class="Customer" column="CID" cascade="save-update"/>
* 小结:
* 1.多对一单向关联时,保存时先保存一方再保存多方,效率较高;
* 2.查询多方时,会自动将其关联的一方查询出来;
* 3.当在多方的配置文件中配置了cascade="save-update"属性时,
* 保存或更新多方时,会级联保存或更新其关联的一方。
1.3映射一对多[双向]关联关系
没有多对一双向的说法,因为它和一对多双向是同一个意思,但是有多对一单向。
理解例子中的一对多双向关联关系:
一个客户[一方]可以对应多个订单[多方],每个订单对应一个客户,即可以从客户自身获取其订单信息(即客户知道自己有哪些订单),也可以从订单自身获取该订单的客户信息(即订单知道自己属于哪个客户)。
实体类:
publicclass Customer {
//客户[一方],一对多双向,可以找到订单
private Integer id; //ID
private String name;//客户名字
private Set<Order> orders = newHashSet<Order>();//多个订单
}
publicclass Order {
//订单[多方],一对多双向,可以找到客户
private Integer id; //ID
private String orderNum;//订单编号
private Customer customer;//客户
}
映射文件:
多方:
<class name="Order" table="ORDERS">
<id name="id"column="ID" type="integer">
<generator class="native"/>
</id>
<property name="orderNum"column="ORDERNUM" type="string"/>
<!-- 多对一映射
name:多方域对象customer属性名
class:多方域对象属性customer的类型
column:多方域对象对应的表orders的外键
<many-to-one name="customer"class="Customer" column="CID"/>
一方:
<class name="Customer" table="CUSTOMERS">
<id name="id"column="ID" type="integer">
<generator class="native"/>
</id>
<property name="name"column="NAME" type="string"/>
<!-- 一对多双向[一方]
Set<Order>orders = new HashSet<Order>()
name:一方的关联属性orders,是一个set集合
table:多方域对象对应的表名
column:多方域对象对应表的外键
class:多方域对象的类名 -->
<set name="orders"table="ORDERS" >
<key column="CID"/>
<one-to-many class="Order"/>
</set>
</class>
保存客户和订单:
Customerc1 = new Customer("张三");
Ordero1 = new Order("N001");
Ordero2 = new Order("N002");
//设置一对多双向关联
c1.getOrders().add(o1);
c1.getOrders().add(o2);
o1.setCustomer(c1);
o2.setCustomer(c1);
/*先保存订单再保存客户
7条sql,3条insert,4条update
session.save(o1);
session.save(o2);
session.save(c1);
*/
/*先保存客户再保存订单
5条sql,3条insert,2条update
效率较高,优先选择保存一方
*/
session.save(c1);
session.save(o1);
session.save(o2);
查询客户或订单:
/*
查询id为1的客户,会将其级联的订单查出来
Customerc1 =(Customer) session.get(Customer.class, 1);
Set<Order>orders = c1.getOrders();
for(Order order : orders) {
System.out.println(order.getOrderNum());
}*/
/*
* 查询id为1的订单,会将其级联的客户查出来
*/
Ordero1 =(Order) session.get(Order.class, 1);
System.out.println(o1.getCustomer().getName());
Customerc1 = new Customer("张三");
Ordero1 = new Order("N001");
Ordero2 = new Order("N002");
//设置一对多双向关联
c1.getOrders().add(o1);
c1.getOrders().add(o2);
o1.setCustomer(c1);
o2.setCustomer(c1);
/*
*保存订单时级联保存客户
* 需在Order.hbm.xml中的many-to-one节点配置
* cascade="save-update"属性
session.save(o1);
session.save(o2);
*/
/*
*保存客户时级联保存订单
* 需在Customer.hbm.xml中的set节点配置
* cascade="save-update"属性
*/
session.save(c1);
更新客户和订单:
有三种方式:
1.手工更新session.update(c1)和sesison.update(o1)
2.session.update(c1)要在Order.hbm.xml中配置属性cascade="save-update"
3.不使用任何session.update()语句,直接更新,原理是原因session一级缓存有快照的功能(快照只适合于更新操作).
Customerc1 = (Customer) session.get(Customer.class, 1); //查询id为1的客户
Ordero4 = (Order) session.get(Order.class, 4);
c1.getOrders().add(o4);//客户变化了
o4.setCustomer(c1);//订单变化了
/*方式一:手工更新,同时调用update方法
session.update(c1);
session.update(o4);*/
//方式二:级联更新,调用update,同时传入一方对象,并在该方配置文件设置cascade="save-update"属性
// session.update(c1);
// session.update(o4);
// 方式三:使用session的快照功能,不调用任何update方法
t.commit();
session.close();
当在双向关联时,应该在一方使用inverse=”true”属性,由多方来负责管理维护双向关系,这样可以减少update语句,如果是多方管理话,有且只有在多方发生引用变化时,hibernate才负责做update操作;如果是多方管理话,多方没有发生引用变化时,即使是一方发生变化,那么hibernate依然不负责update操作;如果单向操作,不要使用inverse属性。
* 小结:
* 1.一对多双向关联时,保存时先保存一方再保存多方,效率较高;
* 2.不管查询哪方都会将其级联的一方查询出来,因为是双向关联;
* 3.当双方都配置了cascade="save-update"属性时,可以级联保存(即保存一方时另一方自动保存)
*4.session有快照功能,当关联发生变化时,可自动更新数据库数据,与cascade="save-update"属性是否设置无关
*/
1.4映射一对一[双向]关联关系
实体类:
publicclass Person {
//人,一对一双向一方,可以找到身份证
private Integer id;
private String name;
private Card card;
}
publicclass Card {
//身份证,一对一双向一方,可以找到人
private Integer id;
private String cardNO; //身份证号码
private String location;
private Person person;
}
映射文件:
有外键一方,即Person.hbm.xml:
<!-- 在一对一双向中,有外键一方用many-to-one标签加上unique="true"属性表示name:Person类中card属性名
class:card属性的类型
column:Person类所对应的表的外键
unique:对外键加上唯一约束,true表示唯一,false表示不唯一
-->
<many-to-one name="card"class="Card" column="CID"unique="true"/>
无外键一方,即Card.hbm.xml:
<!-- 在一对一外键双向关联中,无外健一方使用one-to-one标签
name:Card类的person属性
class:Card类的person属性类型
property-ref:Card类在对方的关联属性,即card属性-->
<one-to-one name="person"class="Person" property-ref="card"/>
1.5映射多对多[双向]关联关系
多对多双向关联可以拆分为多对一单向关联,同过一个中间表(middle),middle表有两个字段,如:tid,sid,分别引用老师表和学生表的id作为外键。而tid和sid有是联合主键。
此时在这种关联状态下,如果由双方管理关联关系,middle会出现主键冲突,所以只能由其中一方来管理。如让老师来管理,则在学生的映射文件中设置数据inverse=“true”。叫由对方管理关联关系。
实体类:
publicclass Student {
//学生,多对多双向的多方,可以找到老师
private Integer id;
private String name;
private Set<Teacher>teacherSet = newHashSet<Teacher>(); //多个老师
}
publicclass Teacher {
//老师,多对多双向的多方,可以找到学生
private Integer id;
private String name;
private Set<Student>studentSet = newHashSet<Student>();//多个学生
}
映射文件:
Student.hbm.xml:
<!-- 在多对多双向关联中,多方使用set标签和many-to-many标签
name:Student的集合属性
table:中间表名
key-column:中间表引用Student表的外键
class:Student类对应的另一个多方
many-to-many-column:中间表引用Teacher表的外键
-->
<set name="teacherSet"table="MIDDLES" inverse="true">
<key column="SID"/>
<many-to-many
class="Teacher" column="TID"/>
</set>
Teacher.hbm.xml:
<!-- 在多对多双向关联中,多方使用set标签和many-to-many标签
name:Teacher类的集合属性
table:中间表
key-column:中间表引用Teacher表的外健
class:Teacher类对应的另一个多方
many-to-many-column:中间表引用Student表的外健
inverse="false"表示双向关联由Teacher来管理
-->
<set name="studentSet"
table="MIDDLES"
cascade="delete">
<key column="TID"/>
<many-to-many
class="Student" column="SID"/>
</set>
1.6继承映射关系(垂直关系)
有三种方案:
A每个类对应[一]张表
比较节约空间,但要有区分字段。使用<subclass>配置,每个类都要加上区分值
discriminator-value=xxx。
B每个类对应[每]张表,每张表独立,无外健关联
使用<union-subclass>配置
C每个类对应[每]张表,子类对应的表通过外健关联(推荐)
使用<joined-subclass>配置
实体类:
publicclass Employee {
private Integer id;
private String name;
}
publicclass HourEmployee extends Employee {
private Double rate;
}
publicclass SalaryEmployee extends Employee {
private Double salary;
}
方案一:
配置文件:
<class name="Employee" table="EMPLOYEES"discriminator-value="E">
<id name="id"column="ID" type="integer">
<generator class="native"/>
</id>
<!-- 区分字段,只能id后面 -->
<discriminator column="ETYPE"type="string"/>
<property name="name"column="NAME" type="string"/>
<!-- 配置子类 discriminator-value:区分字段的值 -->
<subclass name="HourEmployee"discriminator-value="HE">
<property name="rate"column="RATE" type="double"/>
</subclass>
<!-- 配置子类 -->
<subclass name="SalaryEmployee"discriminator-value="SE">
<property name="salary"column="SALARY" type="double"/>
</subclass>
</class>
Employeee = new Employee("员工");
HourEmployeehe = new HourEmployee("时工", 13.5D);
SalaryEmployeese = new SalaryEmployee("月工", 5000D);
session.save(e);
session.save(he);
session.save(se);
方案二:
配置文件:
<class name="Employee" table="EMPLOYEES">
<id name="id"column="ID" type="integer">
<generator class="increment"/><!-- 第二种方案,id生成策略不能用native,因为要保持三张表id的连续性 -->
</id>
<property name="name"column="NAME" type="string"/>
<union-subclass name="HourEmployee"table="HOUREMPLOYEES">
<property name="rate"column="RATE" type="double"/>
</union-subclass>
<union-subclass name="SalaryEmployee"table="SALARYEMPLOYEES">
<property name="salary"column="SALARY" type="double"/>
</union-subclass>
</class>
方案三:
配置文件:
<class name="Employee" table="EMPLOYEES">
<id name="id"column="ID" type="integer">
<generator class="native"/>
</id>
<property name="name"column="NAME" type="string"/>
<joined-subclass name="HourEmployee"table="HOUREMPLOYEES">
<key column="HID"/><!-- 主/外键 -->
<property name="rate"column="RATE" type="double"/>
</joined-subclass>
<joined-subclass name="SalaryEmployee"table="SALARYEMPLOYEES">
<key column="SID"/>
<property name="salary"column="SALARY" type="double"/>
</joined-subclass>
</class>
1.7组件映射
publicclass Customer {
//一对多双向 一方
private Integer id;
private String name;
private Address address; //客户地址,组件
private Set<Order> orderSet = newHashSet<Order>();
}
Customer.hbm.xml文件:
<!-- 组件 映射address属性-->
<component name="address"class="Address">
<property name="province"column="PROVINCE" type="string"/>
<property name="city"column="CITY" type="string"/>
<property name="area"column="AREA" type="string"/>
</component>