一、 多表之间的关系
1. 一对多
建表原则:在多的一方创建外键指向一方的主键
2. 多对多
建表原则:创建一个中间表,中间表中至少两个字段作为外键分别指向多对多双方的主键
3. 一对一
建表原则有两两种
第一种:唯一外键对应:假设一对一种的任意一方为多,在多的一方创建外键指向一方的主键。
第二种:主键对应:一方的主键作为另外的主键
二、 实体之间的数据关系
数据库的表能够描述实体数据之间的关系,通过对象也可以进行描述,所谓的关联映像就是将关联关系映射到数据库种。在对象模型种就是一个或多个引用。
在Hibernate种采用java对象关系来描述数据表之间的关系如下:
- 一对一
class A{
B b;
}
class B{
A a;
}
- 一对多
一个A类对应多个B类类型的情况,需要在A类以Set集合的方式引入B类型的对象,在B类种定义A类类型的Set集合
class A{
Set<B> bs;//B的集合
}
class B{
A a;
}
- 多对多
在A类种定义B类类型的Set集合,在B类中定义A类类型的Set集合。
class A{
Set<B> bs;//B的集合
}
class B{
Set<A> as;//A的集合
}
三、 Hibernate中表达一对多|多对一
我们新建一个客户表以及联系人表。我们都知道一个客户,也就是相当于一个公司。想要联系公司,就需要联系人。联系人是有多个的。
第一步创建表
第二步:创建实体(为了篇幅省去 getter/setter方法)
public class Customer {
private Long cust_id;
private String cust_name;
private String cust_source;
private String cust_industry;
private String cust_level;
private String cust_linkman;
private String cust_phone;
private String cust_mobile;
//使用set集合表达一对多的关系
private Set<LinkMan> linkMen =new HashSet<LinkMan>();
public Set<LinkMan> getLinkMen() {
return linkMen;
}
public void setLinkMen(Set<LinkMan> linkMen) {
this.linkMen = linkMen;
}
}
public class LinkMan {
private Long lkm_id;
private Character lkm_gender;
private String lkm_name;
private String lkm_phone;
private String lkm_email;
private String lkm_qq;
private String lkm_mobile;
private String lkm_memo;
private String lkm_position;
//表达多对一的关系
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
第三步:写orm元数据
客户orm元数据
<hibernate-mapping package="com.aiit.domain">
<class name="Customer" table="cst_customer">
<id name="cust_id" >
<generator class="native"></generator>
</id>
<property name="cust_name" ></property>
<property name="cust_source" ></property>
<property name="cust_industry" ></property>
<property name="cust_level" ></property>
<property name="cust_linkman" ></property>
<property name="cust_phone" ></property>
<property name="cust_mobile" ></property>
<!-- 将一对多关系在配置文件中配置 -->
<!--
name属性:集合属性名
class属性:与本身相关联的对象的完整类名
on-to-many标签:
class属性:多的一方的类的全路径
-->
<set name="linkMen">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
</class>
</hibernate-mapping>
联系人的orm元数据
<hibernate-mapping package="com.aiit.domain">
<class name="LinkMan" table="cst_linkman">
<id name="lkm_id">
<generator class="native"></generator>
</id>
<property name="lkm_name"></property>
<property name="lkm_cust_id"></property>
<property name="lkm_gender"></property>
<property name="lkm_phone"></property>
<property name="lkm_mobile"></property>
<property name="lkm_email"></property>
<property name="lkm_qq"></property>
<property name="lkm_position"></property>
<property name="lkm_memo"></property>
<!-- 多对一 -->
<!--
name属性:引用属性名
column:外键列名
class属性:与本身相关联的对象的完整类名
many-to-one标签,代表多对一
-->
<many-to-one name="customer" column="lkm_cust_id" class="Customer"></many-to-one>
</class>
</hibernate-mapping>
第四步:在主配置文件中引入配置文件
<mapping resource="/com/aiit/domain/Customer.hbm.xml"/>
<mapping resource="/com/aiit/domain/LinkMan.hbm.xml" />
第五步:书写测试代码
public void fun1() {
//1.获得session
Session session = HibernateUtils.openSession();
//2.开启事务
Transaction tx = session.beginTransaction();
//3.操作数据
Customer customer =new Customer();
customer.setCust_name("哇哈哈");
LinkMan lm1 =new LinkMan();
lm1.setLkm_name("gyf");
LinkMan lm2 =new LinkMan();
lm2.setLkm_name("zyl");
//1.表达一对多,客户下有多个联系人
customer.getLinkMen().add(lm1);
customer.getLinkMen().add(lm2);
//2.表达多对一,联系人属于哪个客户
lm1.setCustomer(customer);
lm2.setCustomer(customer);
session.save(customer);
session.save(lm1);
session.save(lm2);
//4.提交事务
tx.commit();
//5.关闭session
session.close();
}
四、 Hibernate中一对多|多对一的级联操作——cascade
上面的操作不大人性化,如果一个客户下面有多个联系人的话,我们需要不断调用session.save()方法进行保存。由此引入级联操作
1. 什么是级联操作?
级联操作就是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作
在orm元数据配置中,对cascade属性来设置对控制是否对关对象采用级联级操作。级联级操作对各种关联关系都是有效的
2. 关于级联操作的具体展示
级联是具有方向性的,如一对多的级联操作和多对一的级联操作。
一对多操作案例:
我们想要在使用save()方法去保存客户的时候,顺带一起把联系人保存。并且如果我们想要删掉一个客户的话,顺带一起将我们的联系人全部删除。
首先我们需要在orm元数据中进行配置
<!--
级联操作:cascade
save-update:级联保存更新
delete:级联删除。我们在删除客户的同时,顺带把联系人一起删除完毕
all: save-update 和delete同时设置完毕
-->
<set name="linkMen" cascade="save-update">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
我们需要在一对多的集合中设置cascade属性。并且根据需要传入不同的参数。
save-update
:级联保存更新delete
:级联删除。我们在删除客户的同时,顺带把联系人一起删除完毕all
: save-update 和delete同时设置完毕
测试级联操作:
public class Demo {
@Test
public void fun1() {
Session session = HibernateUtils.openSession();
Transaction tx = session.beginTransaction();
Customer customer =new Customer();
customer.setCust_name("哇哈哈");
LinkMan lm1 =new LinkMan();
lm1.setLkm_name("gyf");
LinkMan lm2 =new LinkMan();
lm2.setLkm_name("zyl");
//1.表达一对多,客户下有多个联系人
customer.getLinkMen().add(lm1);
customer.getLinkMen().add(lm2);
//2.表达多对一,联系人属于哪个客户
lm1.setCustomer(customer);
lm2.setCustomer(customer);
session.save(customer);
tx.commit();
session.close();
}
}
多对一的操作案例:
<!-- 多对一 -->
<!--
name属性:引用属性名
column:外键列名
class属性:与本身相关联的对象的完整类名
-->
<!--
级联操作:cascade
save-update:级联保存更新
delete:级联删除。我们在删除客户的同时,顺带把联系人一起删除完毕
all: save-update 和delete同时设置完毕
-->
<many-to-one name="customer" column="lkm_cust_id" class="Customer" cascade="save-update"></many-to-one>
</class>
一对多和多对一的级联操作是类似,参数一模一样
开发建议使用save-update,不建议使用delete。如果我们我们将联系人和客户联系,我们仅删除了一个联系人可能导致客户删除完毕。
五、 双向关联产生多条SQL语句—inverse属性
我们在定义了两个实体之间一对多和多对多的关系之后,双方均进行了一次更新,用于维护双方之间的关系。实际上,持久态对象可以自动更新数据库。更新客户的时候,已经修改了一次外键。所以不需要双方再次进行维护外键。这样会影响效率。
所以我们需要找到我们的orm元数据。我们只需要一方放弃外键的维护即可。也就是说关系不是双方维护的,只需要交给某一方去维护就可以了。通常多的一方进行维护。也就是说多的一方不能放弃自己的外键。一的一方进行放弃。
举例:一个老师对应多个学生,一个学生对应一个老师。老师记住所有学生名字是很难的,但是如果让每个学生记住老师姓名并不困难。学生不能放弃老师
<!--
inverse属性:配置关系是否维护
true:customer不维护关系
flase(默认值):维护关系
-->
<set name="linkMen" inverse="true">
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
因此如果我们一的一方放弃维护关系,那么在代码上可以不去书写
//表达一对多,客户下有多个联系人
// c.getLinkMen().add(lm1);
// c.getLinkMen().add(lm2);
由于客户放弃和联系人之间关系,可以省去上述代码
//表达多对一,联系人属于哪个客户
lm1.setCustomer(c);
lm2.setCustomer(c);
其次
- 如果我们维护了关系,我们我们将从表,也就是一这边的表删除的话,那么我们外键中的值也会全部删除,设置为null
- 如果我们不维护关系,并且设置为delete。那么删除客户的同时,也会把我们联系人删完。
<set name="linkMen" inverse="true" cascade=“delete”>
<key column="lkm_cust_id"></key>
<one-to-many class="LinkMan"/>
</set>
六、 Hibernate中表达多对多
第一步:
同样我们举例员工 和 职位。在数据库中的表达
第二步:在实体中进行表达
public class User {
private Long user_id;
private String user_code;
private String user_name;
private String user_password;
private Character user_state;
//表达多对多
private Set<Role> roles = new HashSet<Role>();
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
public class Role {
private Long role_id;
private String role_name;
private String role_memo;
//表达多对多
private Set<User> users = new HashSet<User>();
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
第三步:orm元数据中的多对多表达
user.hbm.xml文件
<hibernate-mapping package="cn.aiit.domain" >
<class name="User" table="sys_user" >
<id name="user_id" >
<generator class="native"></generator>
</id>
<property name="user_code"></property>
<property name="user_name"></property>
<property name="user_password"></property>
<property name="user_state"></property>
<!-- 多对多的关系 -->
<!--
name:集合属性名
table:配置中间名
key下的column,别人引用我的外键
后面的class表示我与那个类是多对多关系
column:外键,我引用别人的外键列名
-->
<set name="roles" table="sys_user_role">
<key column="user_id"></key>
<many-to-many class="Role" column="role_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
role.hbm.xml文件
<hibernate-mapping package="cn.aiit.domain" >
<class name="Role" table="sys_role" >
<id name="role_id" >
<generator class="native"></generator>
</id>
<property name="role_name" ></property>
<property name="role_memo" ></property>
<!-- 多对多的关系 -->
<!--
name:集合属性名
table:配置中间名
key下的column,别人引用我的外键
后面的class表示我与那个类是多对多关系
column:外键,我引用别人的外键列名
-->
<set name="users" table="sys_user_role">
<key column="role_id"></key>
<many-to-many class="User" column="user_id"></many-to-many>
</set>
</class>
</hibernate-mapping>
第四步: 主配置文件中进行配置
<mapping resource="/com/aiit/domain/Customer.hbm.xml" />
<mapping resource= "/com/aiit/domain/LinkMan.hbm.xml"/>
<mapping resource= "/com/aiit/domain/User.hbm.xml"/>
<mapping resource= "/com/aiit/domain/Role.hbm.xml"/>
七、 多对多之间的关联属性——inverse属性
根据上面的配置完毕,我们进行代码测试
public void fun1() {
//1 获得session
Session session = HibernateUtils.openSession();
//2 开启事务
Transaction tx = session.beginTransaction();
//-------------------------------------------------
//3操作
User user1 = new User();
User user2 = new User();
user1.setUser_name("zhangsan");
user2.setUser_name("lisi");
Role role1 = new Role();
Role role2 = new Role();
role1.setRole_name("保安");
role2.setRole_name("保洁");
//用户表达关系
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role1);
user2.getRoles().add(role2);
//角色表达关系
role1.getUsers().add(user1);
role1.getUsers().add(user2);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
session.save(user1);
session.save(user2);
session.save(role1);
session.save(role2);
//-------------------------------------------------
//4提交事务
tx.commit();
//5关闭资源
session.close();
}
此时会进行报错,原因和一对多或者多对多一样。我们多对多关系中,第三方表存在存储冲突。双方均维护关系。导致第三方表中的外键,值存在冲突。所以在多对多关系中,我们必须有一方放弃维护关系至于哪一方需要放弃维护,需要根据我们的业务进行选择。
<!-- 使用inverse属性
true: 放弃维护外键关系
false(默认值):维护关系
-->
<set name="users" table="sys_user_role" inverse="true" >
<key column="role_id" ></key>
<many-to-many class="User" column="user_id" ></many-to-many>
</set>
此时我们在测试中,可以放弃一系列的role1.getUsers().add(user1);等一系列操作
八、 多对多的级联操作
我们在多对多中,给某一用户添加角色属性。级联操作的目的就是简化我们书写代码行数。可以方便我们不对用户进行save()方法。因为我们可以在User的orm元数据,进行文件的配置
<!-- cascade级联操作:
save-update: 级联保存更新
delete:级联删除
all:级联保存更新+级联删除
结论: cascade简化代码书写.该属性使不使用无所谓. 建议要用只用save-update.
如果使用delete操作太过危险.尤其在多对多中.不建议使用.
-->
<set name="roles" table="sys_user_role" cascade="save-update">
<key column="user_id"></key>
<many-to-many class="Role" column="role_id"></many-to-many>
</set>
我们尽量在多对多中不要使用delete,因为多对多使用过于危险,因为进行删除之后,会连同另外一个表的属性全部删除