您的位置:首页 > 其它

Hibernate的映射关系与级联(一对一、一对多、多对多)

2017-07-12 16:29 603 查看

Hibernate的映射关系与级联(一对一、一对多、多对多)

一、概述

长期专业踩坑……怪物猎人要登陆switch了

1、外键注意:

被引用的列必须是其所在表的主键或者唯一列(此处的department表的dept_name)

引用列 和 被引用列应该数据类型一致,并且最好长度一致

如果存在数据,那么引用列中 不能 存在 被引用列中没有的数据

2、配置注意:

一的那边配置了,多的那边不配置,叫单向一对多

一的那边配置了,多的那边也配置,叫双向一对多

一的那边不配置,多的那边配置了,报错

3、字段重复注意:

因为是一给多加字段,所以在多的一边加的字段不要重复,会报错

4、级联删除注意:

在数据库中直接删除级联的表,要先删除子表,再删除主表,一开始就删除主表是不行的,因为他被子表给引用着

5、级联插入注意

级联之后,从表插不进去,为什么?

增加了父亲的话,可以在这个父亲的名下增加相应的孩子

但是你随便增加孩子却不行,孩子必须有父亲,没有父亲的孩子哪里来的?石头蹦?孙悟空?

这就是级联,都是连在一起的,有父亲才有孩子

6、测试的时候不要被之前的表数据给阻碍,所以每次测试前请删除相关的表

如果删除表的时候数据库工具一直在转圈,像死机了一样关闭不行,又不动,操作不了,其实是因为你的Eclipse还在开着刚才运行的代码,每一个都是独立的线程,数据库工具会因为等待其关闭而处于阻塞状态,在Eclipse的Console界面可以看到右上角有红点,表示在运行中,把所有的红点都关掉就可以了

//删除
DROP TABLE IF EXISTS `toy_son`;
DROP TABLE IF EXISTS `son`;
DROP TABLE IF EXISTS `toy`;
DROP TABLE IF EXISTS `father`;
DROP TABLE IF EXISTS `mother`;
DROP TABLE IF EXISTS `father`;

//查询
select * from father;
select * from mother;
select * from son;
select * from toy;
select * from toy_son;


7、其实cascade不是必须的,你使用了cascade就可以只保存主表,从表会跟着保存,你不设置cascade,就需要自己手动保存从表

8、我在下面要用的表的表关系

一个父亲有多个孩子

父亲(Father)

id(f_id,主键)

姓名(f_name)

儿子(Son)

id(s_id,主键)

姓名(s_name)

一个父亲有一个妻子

父亲(Father)

id(f_id,主键)

姓名(f_name)

母亲(Mother)

id(m_id,主键)

姓名(m_name)

多个孩子有多个玩具

玩具(Toy)

id(t_id,主键)

姓名(t_name)

儿子(Son)

id(s_id,主键)

姓名(s_name)

9、先来个总结

1、主键一对多

以一的那方主键作为来个表之间的桥梁,所以是将一那方的字段插到多那方

2、非主键一对多

以一的那方的某个字段作为来个表之间的桥梁,所以是将一那方的字段插到多那方

3、主键多对多

两个表都给出主键,插到一个第三方表,这样产生关系,在第三方表就可以通过我的主键查到与我相关的你的主键,再通过你的主键去查你表里的数据

4、非主键多对多

两个表都给出某一个字段,插到一个第三方表,这样产生关系,在第三方表就可以通过我的字段查到与我相关的你的字段,再通过你的字段去查你表里的数据

5、主键一对一

不需要在对方的表里插字段,因为是一一对应的,所以我的主键的值就是你主键的值

6、非主键一对一

你的某个字段等于我现有的某个字段,一一对应

7、双向

你的表里有与我相关的字段可以查到我,我的表里也有你的字段可以查到你对应的数据

8、单向

你的表里没有与我相关的字段,在你的表里查不到我,但是我的表里有你的字段,可以查到你对应的数据

二、示例

1、主键一对多示例

2、非主键一对多示例

3、主键多对多示例

4、非主键多对多示例

5、主键一对一示例

6、非主键一对一示例

7、双向示例

8、单向示例

下面的xml示例都会省略头部声明,完整的xx.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">

<hibernate-mapping package="com.iamzhuwh.more2more">
<class name="Father" table="FATHER">
<id name="f_id" column="F_ID">
<generator class="native"/>
</id>
<property name="f_name" type="string" column="F_NAME"/>
</class>
</hibernate-mapping>


1、主键一对多示例

儿子与父亲的联系在于父亲的f_id,因为f_id是唯一可以识别父亲的标识,所以将父亲的主键f_id作为外键给儿子,以后凭f_id就可以找到父亲对应的儿子啦

/** 父亲 */
public class Father {
/** 父亲的id,主键 */
private int f_id;
/** 父亲的名字 */
private String f_name;
/** 可能有多个儿子,所以是集合,对应配置文件的<set one-to-many>*/
private Set son = new HashSet();
}

/** 儿子 */
public class Son {
/** 儿子的id,主键 */
private int s_id;
/** 儿子的名字 */
private String s_name;
/** 只有一个父亲,对应配置文件的<many-to-one>*/
private Father father;
}

/** 父亲配置文件,Father.hbm.xml */
<hibernate-mapping package="com.iamzhuwh.one2more">
<!--
对应的实体类:com.iamzhuwh.one2more.Father
定义class标签,  <class name="Father" table="FATHER">
name是实体类名字,
table是你要创建的表名
包名:package="com.iamzhuwh.one2more"
-->
<class name="Father" table="FATHER">
<!-- id是主键,自增 -->
<id name="f_id" column="F_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="f_name" type="string" column="F_NAME"/>
<set name="son" inverse="false" cascade="all" >
<!--
一的一方的外键column="f_id"
儿子与父亲的联系在于父亲的f_id,因为主键f_id是唯一可以识别父亲的标识
-->
<key column="f_id"></key>
<one-to-many class="com.iamzhuwh.one2more.Son"/>
</set>
</class>
</hibernate-mapping>

<!-- 儿子配置文件,Son.hbm.xml -->
<hibernate-mapping package="com.iamzhuwh.one2more">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Son" table="SON">
<!-- id是主键,自增 -->
<id name="s_id" column="S_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="s_name" type="string" column="S_NAME"/>
<property name="s_f_id" type="int" column="S_F_ID"/>
<!--
一的一方的外键column="f_id"
儿子与父亲的联系在于父亲的f_id,因为主键f_id是唯一可以识别父亲的标识
-->
<many-to-one name="father" column="f_id" class="com.iamzhuwh.one2more.Father">
</many-to-one>
</class>
</hibernate-mapping>

/** 测试代码 */
public class Test {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
/** 父亲 */
Father father = new Father();
father.setF_name("f1");
/** 儿子 */
Son son = new Son();
son.setS_name("s1");
/**
级联
增加了父亲的话,可以在这个父亲的名下增加相应的孩子
但是你随便增加孩子却不行,孩子必须有父亲,没有父亲的孩子哪里来的?石头蹦?孙悟空?
这就是级联,都是连在一起的
*/
father.getSon().add(son);
/** 获取连接 */
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
/** 因为是连在一起的,所以保存了父亲,父亲名下的孩子也会被保存 */
System.out.println("Father:"+session.save(father));
/** 提交事务 */
session.getTransaction().commit();
}


2、非主键一对多示例

以上面的代码为基础,上面提到了父亲的主键f_id作为外键给儿子,以后凭f_id就可以找到父亲对应的儿子,那么如果父亲给的外键不是主键呢?比如f_name,父亲的名字

使用非主键作为外键,这个外键必须是唯一不可重复的,所以要设置unique=”true”

在一的那边使用属性property-ref,即是

/** 父亲配置 */
<hibernate-mapping package="com.iamzhuwh.one2more">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Father" table="FATHER">
<!-- id是主键,自增 -->
<id name="f_id" column="F_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!--
这里变一下
unique="true"
-->
<property name="f_name" type="string" column="F_NAME" unique="true"/>
<set name="son" inverse="false" cascade="all" >
<!--
**** 重要 ****
这里变一下 property-ref="f_name"
用父亲表的父亲名字f_name作为外键,赋予给儿子
key column="s_f_name",这句话只是给加在儿子表的那个字段起个名字叫s_f_name而已
使用property-ref="f_name"才是真正将f_name的值赋予给s_f_name
-->
<key column="s_f_name" property-ref="f_name"></key>
<one-to-many class="com.iamzhuwh.one2more.Son"/>
</set>
</class>
</hibernate-mapping>
<!-- 儿子配置 -->
<hibernate-mapping package="com.iamzhuwh.one2more">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Son" table="SON">
<!-- id是主键,自增 -->
<id name="s_id" column="S_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="s_name" type="string" column="S_NAME"/>
<property name="s_f_id" type="int" column="S_F_ID"/>
<!--
这里变一下,column="s_f_name"
-->
<many-to-one name="father" column="s_f_name" class="com.iamzhuwh.one2more.Father">
</many-to-one>
</class>
</hibernate-mapping>


3、主键多对多示例

多对多的时候,要记住,不是简单给个字段对方而已,要建一个中间表,将双方的外键放上去

/** 儿子表的实体 */
public class Son {
/** 儿子的id,主键 */
private int s_id;
/** 儿子的名字 */
private String s_name;
/** 儿子的玩具,一个儿子可以拥有多个玩具 */
private Set toys = new HashSet();
}

/** 玩具表的实体 */
public class Toy {
/** 玩具的id,主键 */
private int t_id;
/** 玩具的名字 */
private String t_name;
/** 每个玩具可以被多个人拥有*/
private Set sons = new HashSet();
}

<!-- 儿子表的配置 -->
<hibernate-mapping package="com.iamzhuwh.more2more">
<!-- SON表 -->
<class name="Son" table="SON">
<!-- 主键,自增 -->
<id name="s_id" column="S_ID">
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="s_name" type="string" column="S_NAME"/>
<!--
儿子可以有多个玩具,所以将儿子的s_id作为外键,<key column="S_ID"></key>
玩具可以有多个主人,所以将玩具的t_id作为外键,<many-to-many column="T_ID"
如果让玩具表来处理级联的增删改,需要在玩具的Set标签中配置inverse与cascade
**** 重点 ****
这个table填的是TOY_SON!!!<set name="toys" table="TOY_SON"
根据上面一对多的经验,我们是要外键给对方,所以这里填的应该是对方的表名才对吧?
但是这里为啥填的是TOY_SON?为啥不是TOY或者SON?

因为要新建一张第三方叫TOY_SON的表,来存放儿子与玩具的外键

<set name="toys",这里填的是你在Son.java中配置的Set集合的名字:
private Set toys = new HashSet();/** 儿子的玩具,一个儿子可以拥有多个玩具*/
-->
<set name="toys" table="TOY_SON" inverse="true" cascade="all">
<!-- column="S_ID",代表是Son给出的外键是S_ID -->
<key column="S_ID"></key>
<many-to-many column="T_ID" class="com.iamzhuwh.more2more.Toy"></many-to-many>
<!-- column="T_ID",代表是com.iamzhuwh.more2more.Toy给出的外键是T_ID -->
</set>
</class>
</hibernate-mapping>

<!-- 玩具表的配置 -->
<hibernate-mapping package="com.iamzhuwh.more2more">
<!-- TOY表 -->
<class name="Toy" table="TOY">
<!-- id是主键,自增 -->
<id name="t_id" column="T_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="t_name" type="string" column="T_NAME"/>
<!--
玩具可以有多个主人,所以将玩具的t_id作为外键,<key column="T_ID"></key>
儿子可以有多个玩具,所以将儿子的s_id作为外键,<many-to-many column="S_ID"
**** 注意 ****
如果让玩具表来处理级联的增删改,需要在玩具的Set标签中配置inverse与cascade
但是上面已经让儿子来处理了,所以在玩具这里不用配置inverse与cascade
**** 重点 ****
这个table填的是TOY_SON!!!<set name="sons" table="TOY_SON">
根据上面一对多的经验,我们是要外键给对方,所以这里填的应该是对方的表名才对吧?
但是这里为啥填的是TOY_SON?为啥不是TOY或者SON?

因为要新建一张第三方叫TOY_SON的表,来存放儿子与玩具的外键

<set name="sons",这里填的是你在Toy.java中配置的Set集合的名字:
private Set sons = new HashSet();/** 每个玩具可以被多个人拥有*/
-->
<set name="sons" table="TOY_SON">
<!-- column="T_ID",代表Toy给出的外键是T_ID -->
<key column="T_ID"></key>
<many-to-many column="S_ID" class="com.iamzhuwh.more2more.Son"></many-to-many>
<!-- column="S_ID",代表是Son给出的外键是S_ID -->
</set>
</class>
</hibernate-mapping>

/** 测试代码 */
public class Test {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
/** 玩具 */
Toy toy = new Toy();
toy.setT_name("t1");
/** 儿子 */
Son son = new Son();
son.setS_name("s1");
/** 级联
因为在Son配置表中配置了<set name="toys" table="TOY_SON" inverse="true" cascade="all">
所以级联让Toy来处理,增加了Toy的话,可以把玩具赋给相应的孩子
*/
toy.getSons().add(son);
son.getToys().add(toy);
/** 获取连接 */
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
/** 因为是连在一起的,所以保存了儿子,儿子名下的玩具也会被保存 */
System.out.println("Son:"+session.save(son));
/** 提交事务 */
session.getTransaction().commit();
}
}


4、非主键多对多示例

上面是主键多对多示例,所以使用主键id作为外键,这次是非主键作为外键,所以我们使用名字name作为外键

<hibernate-mapping package="com.iamzhuwh.more2more">
<class name="Son" table="SON">
<id name="s_id" column="S_ID">
<generator class="native"/>
</id>
<!--
这次要用这个s_name作为外键,所以要加一个unique="true"
让其变为唯一可以代表这个儿子的标识
-->
<property name="s_name" type="string" column="S_NAME" unique="true"/>

<!--
<key column="S_NAME" property-ref="s_name"></key>
解析:
column="S_NAME":
在TOY_SON表中创建一个字段名字叫S_NAME
property-ref="s_name":
这个字段的类型或者值请参考Son配置表的property name="s_name"
————————————————————————————————————————————————————
<many-to-many column="T_NAME" property-ref="t_name"
解析:
column="T_NAME":
在TOY_SON表中创建一个字段名字叫T_NAME
property-ref="t_name":
这个字段的类型或者值请参考Toy配置表的property name="t_name"
-->
<set name="toys" table="TOY_SON" inverse="true" cascade="all" >
<key column="S_NAME" property-ref="s_name"></key>
<many-to-many column="T_NAME" property-ref="t_name" class="com.iamzhuwh.more2more.Toy"></many-to-many>
</set>
</class>
</hibernate-mapping>

<hibernate-mapping package="com.iamzhuwh.more2more">
<class name="Toy" table="TOY">
<id name="t_id" column="T_ID">
<generator class="native"/>
</id>
<!--
这次要用这个t_name作为外键,所以要加一个unique="true"
让其变为唯一可以代表这个玩具的标识
-->
<property name="t_name" type="string" column="T_NAME" unique="true"/>
<!--
<key column="T_NAME" property-ref="t_name"></key>
解析:
column="T_NAME":
在TOY_SON表中创建一个字段名字叫T_NAME
property-ref="t_name":
这个字段的类型或者值请参考Toy配置表的property name="t_name"
————————————————————————————————————————————————————
<many-to-many column="S_NAME" property-ref="s_name"
解析:
column="S_NAME":
在TOY_SON表中创建一个字段名字叫S_NAME
property-ref="s_name":
这个字段的类型或者值请参考Son配置表的property name="s_name"
-->
<set name="sons" table="TOY_SON">
<key column="T_NAME" property-ref="t_name"></key>
<many-to-many column="S_NAME" property-ref="s_name" class="com.iamzhuwh.more2more.Son"></many-to-many>
</set>

</class>
</hibernate-mapping>


5、主键一对一示例

这个一对一,你查看数据库表,是没有多出字段的,为什么?因为是一一对应的,比如我用我的id就可以找到你的id,因为是一一对应,所以不需要在你的表里插一个我的id

对比一堆多,一的那边会在多的那边插入一个字段,比如id,来表示两者之间存在关系,通过这个id我就可以查找到跟我关联的你的表的数据

使用one to one 的方式展示主键一对一示例

/** 孩子他爸 */
public class Father {
/** 丈夫的id,主键 */
private int f_id;
/** 丈夫的名字 */
private String f_name;
/** 只有一个妻子*/
private Mother mother;
}

/** 孩子他妈 */
public class Mother {
/** 妻子的id,主键 */
private int m_id;
/** 妻子的名字 */
private String m_name;
/** 只有一个丈夫 */
private Father father;
}

<!-- 孩子他爸的配置 -->
<hibernate-mapping package="com.iamzhuwh.one2one">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Father" table="FATHER">
<!-- id是主键,自增 -->
<id name="f_id" column="F_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="f_name" type="string" column="F_NAME"/>
<!--
父母是一对一的,一个丈夫对应一个妻子,所以在丈夫中引入妻子
<one-to-one name="mother"
这里就是配置了妻子的信息
name="mother"代表在Father.java中创建了一个叫mother的变量
参考Father.java:
/** 只有一个妻子*/
private Mother mother;
property-ref="m_id"代表引用Mother中的m_id作为外键
参考Mother.hbm.xml:
<id name="m_id" column="M_ID">
-->
<one-to-one name="mother" property-ref="m_id" cascade="all" class="com.iamzhuwh.one2one.Mother"></one-to-one>
</class>
</hibernate-mapping>

<!-- 孩子他妈的配置 -->
<hibernate-mapping package="com.iamzhuwh.one2one">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Mother" table="MOTHER">
<!-- id是主键,自增 -->
<id name="m_id" column="M_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="m_name" type="string" column="M_NAME"/>
<!--
父母是一对一的,一个丈夫对应一个妻子,所以在妻子中引入丈夫
<one-to-one name="father"
这里就是配置了丈夫的信息
name="father"代表在Mother.java中创建了一个叫father的变量
参考Mother.java:
/** 只有一个丈夫 */
private Father father;
property-ref="f_id"代表引用Father中的f_id作为外键
参考Father.hbm.xml:
<id name="f_id" column="F_ID">
-->
<one-to-one name="father" property-ref="f_id" class="com.iamzhuwh.one2one.Father"></one-to-one>
</class>
</hibernate-mapping>

/** 测试代码 */
public class Test {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
/** 孩子爸 */
Father father = new Father();
father.setF_name("f1");
/** 孩子妈 */
Mother mother = new Mother();
mother.setM_name("m1");
/** 级联
增加丈夫,也可以增加妻子
*/
father.setMother(mother);
mother.setFather(father);
/** 获取连接 */
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
/**
因为在孩子他爸的配置表里面配置了cascade="all",
所以这里只需要保存孩子爸,孩子妈就会因为级联而被保存
如果没有cascade,则孩子妈需要手动保存
*/
System.out.println("Father:"+session.save(father));
/** 提交事务 */
session.getTransaction().commit();
}
}


使用many to one 的方式展示主键一对一示例

还是在上面的基础上改,改父亲配置表里面的孩子妈配置,将one to one改为more to one,给more to one的字段配置唯一约束unique,它的效果就相当于变成one to one了

<!-- 修改孩子妈配置表 -->
<many-to-one name="father" column="F_ID" class="com.iamzhuwh.one2one.Father" unique="true"></many-to-one>
<!-- 孩子爸配置表还是不变 -->
<one-to-one name="mother" cascade="all" class="com.iamzhuwh.one2one.Mother"></one-to-one>


6、非主键一对一示例

按上面的经验,在使用one to one的基础上,修改为非主键一对一,以名字作为外键,设置名字唯一的约束unique,其他不变

使用one to one 的方式展示非主键一对一示例

<!-- 孩子爸配置表 -->
<property name="f_name" type="string" column="F_NAME" unique="true"/>
<!--引用孩子妈的名字作为外键, property-ref="m_name" -->
<one-to-one name="mother" property-ref="m_name" cascade="all" class="com.iamzhuwh.one2one.Mother"></one-to-one>

<!-- 孩子妈配置表 -->
<property name="m_name" type="string" column="M_NAME" unique="true"/>
<!--引用孩子爸的名字作为外键, property-ref="f_name" -->
<one-to-one name="father" property-ref="f_name" class="com.iamzhuwh.one2one.Father"></one-to-one>


使用many to one 的方式展示非主键一对一示例

<!-- 在上面的进程上修改孩子妈配置表 <one-to-one变<many-to-one -->
<many-to-one name="father" property-ref="f_name" class="com.iamzhuwh.one2one.Father" unique="true"></many-to-one>


7、双向示例

这个是一对多的双向示例,双向就是说通过我可以查到你,通过你可以查到我

/** 父亲 */
public class Father {
/** 父亲的id,主键 */
private int f_id;
/** 父亲的名字 */
private String f_name;
/** 可能有多个儿子,所以是集合,对应配置文件的<set one-to-many>*/
private Set son = new HashSet();
}

/** 儿子 */
public class Son {
/** 儿子的id,主键 */
private int s_id;
/** 儿子的名字 */
private String s_name;
/** 只有一个父亲,对应配置文件的<many-to-one>*/
private Father father;
}

/** 父亲配置文件,Father.hbm.xml */
<hibernate-mapping package="com.iamzhuwh.one2more">
<!--
对应的实体类:com.iamzhuwh.one2more.Father
定义class标签,  <class name="Father" table="FATHER">
name是实体类名字,
table是你要创建的表名
包名:package="com.iamzhuwh.one2more"
-->
<class name="Father" table="FATHER">
<!-- id是主键,自增 -->
<id name="f_id" column="F_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="f_name" type="string" column="F_NAME"/>
<set name="son" inverse="false" cascade="all" >
<!--
一的一方的外键column="f_id"
儿子与父亲的联系在于父亲的f_id,因为主键f_id是唯一可以识别父亲的标识
-->
<key column="f_id"></key>
<one-to-many class="com.iamzhuwh.one2more.Son"/>
</set>
</class>
</hibernate-mapping>

<!-- 儿子配置文件,Son.hbm.xml -->
<hibernate-mapping package="com.iamzhuwh.one2more">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Son" table="SON">
<!-- id是主键,自增 -->
<id name="s_id" column="S_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="s_name" type="string" column="S_NAME"/>
<property name="s_f_id" type="int" column="S_F_ID"/>
<!--
一的一方的外键column="f_id"
儿子与父亲的联系在于父亲的f_id,因为主键f_id是唯一可以识别父亲的标识
-->
<many-to-one name="father" column="f_id" class="com.iamzhuwh.one2more.Father">
</many-to-one>
</class>
</hibernate-mapping>

/** 测试代码 */
public class Test {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
/** 父亲 */
Father father = new Father();
father.setF_name("f1");
/** 儿子 */
Son son = new Son();
son.setS_name("s1");
/**
级联
增加了父亲的话,可以在这个父亲的名下增加相应的孩子
但是你随便增加孩子却不行,孩子必须有父亲,没有父亲的孩子哪里来的?石头蹦?孙悟空?
这就是级联,都是连在一起的
*/
father.getSon().add(son);
/** 获取连接 */
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
/** 因为是连在一起的,所以保存了父亲,父亲名下的孩子也会被保存 */
System.out.println("Father:"+session.save(father));
/** 提交事务 */
session.getTransaction().commit();
}


8、单向示例

这个是一对多的单向示例,单向就是说通过我可以查到你,你却查不到我

/** 父亲 */
public class Father {
/** 父亲的id,主键 */
private int f_id;
/** 父亲的名字 */
private String f_name;
/** 可能有多个儿子,所以是集合,对应配置文件的<set one-to-many>*/
private Set son = new HashSet();
}

/** 儿子 */
public class Son {
/** 儿子的id,主键 */
private int s_id;
/** 儿子的名字 */
private String s_name;
/** 只有一个父亲,对应配置文件的<many-to-one>*/
private Father father;
}

/** 父亲配置文件,Father.hbm.xml */
<hibernate-mapping package="com.iamzhuwh.one2more">
<!--
对应的实体类:com.iamzhuwh.one2more.Father
定义class标签,  <class name="Father" table="FATHER">
name是实体类名字,
table是你要创建的表名
包名:package="com.iamzhuwh.one2more"
-->
<class name="Father" table="FATHER">
<!-- id是主键,自增 -->
<id name="f_id" column="F_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="f_name" type="string" column="F_NAME"/>
<set name="son" inverse="false" cascade="all" >
<!--
一的一方的外键column="f_id"
儿子与父亲的联系在于父亲的f_id,因为主键f_id是唯一可以识别父亲的标识
-->
<key column="f_id"></key>
<one-to-many class="com.iamzhuwh.one2more.Son"/>
</set>
</class>
</hibernate-mapping>

<!-- 儿子配置文件,Son.hbm.xml -->
<hibernate-mapping package="com.iamzhuwh.one2more">
<!-- 对应的实体类,name是实体类名字,table是你要创建的表名 -->
<class name="Son" table="SON">
<!-- id是主键,自增 -->
<id name="s_id" column="S_ID">
<!-- 主键配置 -->
<generator class="native"/>
</id>
<!-- 其他字段 -->
<property name="s_name" type="string" column="S_NAME"/>
<!--
一的一方的外键column="f_id"
儿子与父亲的联系在于父亲的f_id,因为主键f_id是唯一可以识别父亲的标识
因为现在是单向,所以直接把这里给去掉,这样在父亲表就找不到儿子的踪影了,但是在儿子表可以找到父亲的id
<many-to-one name="father" column="f_id" class="com.iamzhuwh.one2more.Father">
</many-to-one>
-->
</class>
</hibernate-mapping>

/** 测试代码 */
public class Test {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
/** 父亲 */
Father father = new Father();
father.setF_name("f1");
/** 儿子 */
Son son = new Son();
son.setS_name("s1");
/**
级联
增加了父亲的话,可以在这个父亲的名下增加相应的孩子
但是你随便增加孩子却不行,孩子必须有父亲,没有父亲的孩子哪里来的?石头蹦?孙悟空?
这就是级联,都是连在一起的
*/
father.getSon().add(son);
/** 获取连接 */
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
/** 因为是连在一起的,所以保存了父亲,父亲名下的孩子也会被保存 */
System.out.println("Father:"+session.save(father));
/** 提交事务 */
session.getTransaction().commit();
}


三、踩坑

问题1

Foreign key (FK_9jbtfsoc50vbxnpaladvwbd50:SON [s_id])) must have same number of columns as the referenced primary key (SON [t_id,s_id])

添加property-ref即可,如:

<key column="S_ID" property-ref="s_id"></key>


问题2

Field ‘S_ID’ doesn’t have a default value 或者 Field ‘T_ID’ doesn’t have a default value

我的原因是跟inverse相关,也有人说是在数据库给字段加默认值,或者是主键不要native,但是我的主键本来就是非空且自增,不可能是默认值或者native的问题

问题3

object references an unsaved transient instance - save the transient instance before flushing: com.iamzhuwh.more2more.Son

son做级联,却保存toy,所以报错,应该是save(son),然后根据级联,son会将里面的toy拿出来保存

问题4

注意两个事情:(以下面代码为例)

首先这里定义了一个字段:

<property name="t_name" type="string" column="T_NAME" unique="true"/>


name=”t_name”是用来在这个配置表中代表这个字段的

column=”T_NAME”是用来代表在数据库中要展示的字段名

所以这里要这样引用:

<key column="T_NAME" property-ref="t_name"></key>


在数据库定义一个字段叫T_NAME

这个字段的值引用在本配置文件中配置的t_name的信息

<class name="Toy" table="TOY">
<id name="t_id" column="T_ID">
<generator class="native"/>
</id>
<property name="t_name" type="string" column="T_NAME" unique="true"/>
<set name="sons" table="TOY_SON">
<key column="T_NAME" property-ref="t_name"></key>
<many-to-many column="S_NAME" class="com.iamzhuwh.more2more.Son"></many-to-many>
</set>


四、课外知识

1、为什么会有怎么多种关系?

一对多、多对一、单向、双向是针对不同的情况和业务需求已定的,其实本质是一样的,只是操作的方式不同

1、什么是单向与双向?

例如:领导叫我查用户与包裹的信息

单向一对多:

用户A对于多个包裹,领导叫我在用户表查找用户A的地址,我就查呗

其实我查询包裹上的信息也可以查到用户A的地址,但是没必要,因为领导说了,到用户表去查

又或者我没有查询包裹信息的权限,所以只有从用户表查

单向多对一:

多个包裹对于用户A,领导叫我在包裹信息表里面查用户A的地址,我就查呗

其实我查询用户表上的信息也可以查到用户A的地址,但是没必要,因为领导说了,到包裹信息表去查

又或者我没有查询用户表的权限,所以只有从包裹信息表查

双向一对多/双向多对一:

领导又来了,叫我查用户A的地址,不管我怎么查,我从用户表可以查到,从包裹信息表也可以查到

懂了么?

这就是针对不同的业务需求决定使用哪种映射关系,但是本质都是可以查到目标信息

2、什么是一对多与多对一?

例如:用户与包裹

用户A买了一堆的东西,那就是一个用户对应多个包裹,也就是一对多

一堆的包裹的收件人都是用户A,那就是多个包裹对应一个用户,也就是多对一

3、主键与外键

a、什么是主键

解释:

一张表中的一条数据,可能会包含多个属性,比如姓名、性别

已知一张表有多条数据,用什么来区分每条数据?就是用主键了

不为空的,不重复的,能代表一条数据的,叫主键

什么东西可以作为主键?

一般是XX_id,从1开始自动递增,保证不重复

也可以是一组属性,比如姓名、年龄、性别加起来作为一个主键

例子:

例如一个人,身份证号码可以作为他的唯一标示,也就是主键

例如一台电视,品牌、型号、序列号加起来可以作为它的唯一标示,也就是主键

b、什么是外键

解释:

在自己的表中,有一个字段,可以确定另一张表的数据的唯一性,叫外键

这个字段,对另一张表来说,叫主键,对自己的这张表来说,叫外键

a表与b表,a表中的某一个字段可以区分b表的每一条数据,这个字段就是a表的外键,是b表的主键

例子:

比如一件快递包裹

在天猫商家的系统里,有这个包裹的购买人id、订单号、电话、快递单号等信息,主键是订单号,通过订单号区分每一个订单

在快递公司的系统里,有这个包裹的快递单号、收件人、电话、地址等信息,主键是快递单号,通过快递单号区分每一个包裹

对快递公司来说,这个快递单号是主键

对天猫商家来说,这个快递单号是外键

我通过订单号可以查到所有订单信息,然后从中找到快递单号,然后在快递表里查快递单号,就可以查到包裹的物流信息

c、区别

主键

唯一标识一条记录,不能有重复的,不允许为空

用来保证数据完整性

一张表主键只能有一个

外键

表的外键是给另一张表使用的,这个外键在另一张表里可以重复,可以是空值。但是在自己表里必须是唯一的,不然你怎么么代表自己表里的一条数据

用来和其他表建立联系用的

一个表可以有多个外键

4、一对一

a、单向一对一主键关联

简单来说,我的主键,就是我的外键,也就是你的主键,一样的值

单向的意思就是,只可以我用我的外键查你,你不查我

b、双向一对一主键关联

简单来说,我的主键,就是我的外键,也就是你的主键,一样的值

双向的意思就是,我可以查你,你可以查我

c、单向一对一外键关联

简单来说,我的主键很宝贵,不给你,那么我的其他字段呢,可以作为我的外键,给你当主键用

单向的意思就是,只可以我用我的外键查你,你不查我

d、双向一对一外键关联

简单来说,我的主键很宝贵,不给你,那么我的其他字段呢,可以作为我的外键,给你当主键用

双向的意思就是,我可以查你,你可以查我

e、一对一外键关联的注意点

因为是一对一,所以是唯一的,所以我的外键也要是唯一的,所以要设置属性unique=true

那为什么一对一主键关联不需要设置这个属性呢,因为两边都是主键啊,主键本来就是唯一且非空的

2、什么是级联

比如主表改了数据,子表相关的数据也会跟着变化,这就是级联

例如用户表里面,用户的地址改了,那么包裹的地址也要跟着变化

用实体类来说,Father就是那个主表,里面有一个set集合的变量,son就是set集合中的一个元素,所以是father.getSon().add(son);

- cascade参数

cascade=”all

所有情况下均进行关联操作,包括save、update、delete

cascade=”none”

所有情况下均不进行关联操作,这是默认值

cascade=”save-update”

仅save、update、saveOrUpdate时进行关联操作

cascade=”delete”

仅delete时进行关联操作

cascade=”all-delete-orphan”

用户对应多个包裹,其中一个包裹,用户不要了,这个包裹就被干掉了

当一个节点在对象图中成为孤儿节点时,删除该节点(摘自百度)

- inverse参数

这个参数决定谁来维护这个关联关系,它只在主表的那个配置文件里面配置

即Father的配置文件,

什么是关联关系,即是主表更新了,主表要去更新一下子表吗?还是让子表自己更新?

inverse=”false”,代表指定Father维护这个关联关系,即主表更新的同时,子表也更新了,然后主表还要更新一下子表

比如有Father表,有f_id,Son表,也有f_id,因为两个是关联的嘛,然后更新Father表,Son表也会一起更新,但是这个时候Son表的f_id是空的,因为Father表还没更新完

待Father表更新完了,然后Son表也更新完了,Father表会将新增的那个f_id扔给Son表去update一下刚才空的f_id

inverse=”true”,代表指定Son维护这个关联关系,即主表更新后,子表再更新更新

比如有Father表,有f_id,Son表,也有f_id,因为两个是关联的嘛,然后更新Father表,Son表是不会有动静的,待Father表更新完了,把f_id扔给Son表,然后Son表再默默的更新自己的信息

懂了不,再看例子

Father表配置
<set name="son" inverse="true" cascade="all" >


Hibernate: insert into FATHER (F_NAME) values (?)

//inverse="true",代表指定Son维护这个关联关系,即主表更新了,子表自己更新

Hibernate: insert into SON (S_NAME, f_id) values (?, ?)


Father表配置
<set name="son" inverse="false" cascade="all" >


Hibernate: insert into FATHER (F_NAME) values (?)
Hibernate: insert into SON (S_NAME, f_id) values (?, ?)

//这里多了一次更新操作,为啥?
//因为inverse="false",代表指定Father维护这个关联关系,即主表更新了,子表更新了,主表还要去更新一下子表
//所以主表更新了自己之后,又去更新了一下子表的F_ID

Hibernate: update SON set f_id=? where S_ID=?


cascade与inverse

这个时候,大家可能又懵逼了,什么鬼啊,cascade=all的时候,不是已经级联了吗,不是主表更新子表也跟着更新了吗?!!

其实是这样的,主表更新,子表也跟着更新,这个是cascade级联的作用

粗暴的理解:让主表还是从表去控制子表的更新,这个是inverse的作用。配合上面的例子慢慢体会

3、主键的类别:

Assigned

由用户生成主键值,并且要在save()之前指定否则会抛出异常

Hilo

使用高低位算法生成主键,高低位算法使用一个高位值和一个低位值,然后把算法得到的两个值拼接起来作为数据库中的唯一主键

Increment

对主键值采取自动增长的方式生成新的主键值,但要求底层数据库的主键类型为long,int等数值型。主键按数值顺序递增,增量为1。

主键值由Hibernate本身维护,适用于所有的数据库,不适合多进程并发更新数据库

Identity

根据底层数据库,来支持自动增长

例如MySQl中是auto_increment, SQL Server 中是Identity

Sequence

需要底层数据库支持序列,例如Oracle数据库等

Native

根据不同的底层数据库自动选择Identity、Sequence、Hilo主键生成方式

便于程序移植,项目中如果用到多个数据库时,可以使用这种方式。

UUID

使用128位UUID算法生成主键,能够保证网络环境下的主键唯一性,也就能够保证在不同数据库及不同服务器下主键的唯一性

Foreign GUID

用于一对一关系中。GUID主键生成方式使用了一种特殊算法,保证生成主键的唯一性,支持SQL Server和MySQL
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: