Hibernate关联映射
关联映射大体分为
关联的方向
及关联有方向,大抵意思是一方能够通过关联查到对方,
这就具备了单向关联,如果双方能互相查到,就可以称为双向关联
例:
public class Person {
private int id;
private String name;
private IdCard card;
....
}
public class IdCard {
private int id;
private String Numbers;
....
}
如果IdCard中有Person对象
public class IdCard {
private int id;
private String Numbers;
private Person person;
....
}
这种关联很容易理解,
但是这只是在实体对象层面上的关联
数据库中的关联
一般以设置外键参照其他表的主键建立表与表的关联**(外键关联)**
但也可以将主键作为外键(既是PK,也是FK)与其他表的主键建立关联**(主键关联)**
取决于业务的选择,通常可以将这三大类关系按上述的关联进行划分
一对一关系映射
1.一对一单向主键关联 |
---|
2.一对一双向主键关联 | 3.一对一单向外键关联 | 4.一对一双向外键关联 |
1.一对一单向主键关联
以Person类和IdCcard为例:
他们的实体类的关系如,是典型的单向关联
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dEPjjJOY-1630160728327)(F:\LocalTyproPictrue\ss1-16298934971211.png)]
数据库表的关系如
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HnvQ0IY4-1630160728330)(F:\LocalTyproPictrue\874710-20161203121004193-814894327.png)]
person表的id既是主键,又是外键,要参照idcard表的主键
所以person的主键必须和idcard的主键相同,建立了主键关联
public class Person {
private int id;
private String name;
private IdCard idCard;
}
Person.hbm.xml
<class name="pojo.Person" table="t_person">
<id name="id" type="int">
<!-- 主键生成策略 -->
<generator class="foreign">
<!--,因为主键同时也是外键,所以主键的生成参照属性idCard的类型的主键-->
<param name="property">idCard</param>
</generator>
</id>
<!-- 实体类的属性 -->
<property name="name" type="string"/>
<!--constrained=true表明当前主键作为外键参照了idCard属性(的主键)-->
<one-to-one name="idCard" constrained="true"></one-to-one>
</class>
public class IdCard {
private int id;
private String cardNo;
....
}
<class name="pojo.IdCard" table="t_card">
<id name="id" column="id">
<!-- 主键生成策略 使用native -->
<generator class="native">
</generator>
</id>
<!-- 一些常规属性 -->
<property name="cardNo"></property>
</calss>
测试:
IdCard card = new IdCard();
card.setCardNo("99999");
session.save(card);
Person person = new Person();
person.setName("杰洛特");
person.setIdCard(card);
session.save(person);
session.beginTransaction().commit();
生成表
Hibernate:
create table t_idcard (
id integer not null auto_increment,
cardNo varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
create table t_person (
id integer not null,
name varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
alter table t_person //向t_person表中添加t_person的id外键参照t_idcard的id
add constraint FKn42oi4jn933kff11t0rmgmv6a
foreign key (id)
references t_idcard (id)
插入语句
Hibernate:
insert
into
t_idcard
(cardNo)
values
(?)
Hibernate:
insert
into
t_person
(name, id)
values
(?, ?)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gDBaSOxe-1630160728332)(F:\LocalTyproPictrue\1231.png)]
加载
Person person = session.load(Person.class, 1);
System.out.println(person.getIdCard().getCardNo());
System.out.println(person);
结果
Hibernate:
select
person0_.id as id1_2_0_,
person0_.name as name2_2_0_
from
t_person person0_
where
person0_.id=? //先查t_person表
Hibernate:
select
idcard0_.id as id1_1_0_,
idcard0_.cardNo as cardno2_1_0_
from
t_idcard idcard0_ //再通过t_person的主键(也是外键)查idcard表
where
idcard0_.id=?
99999
Person [id=1, name=杰洛特, card=IdCard [id=1, Numbers=99999]]
2.一对一双向主键关联
同理将IdCard类的加上Person的属性,变成双向关联
public class IdCard {
private int id;
private String cardNo;
private Person person;
....
}
<class name="pojo.IdCard" table="t_card">
<id name="id" column="id">
<!-- 主键生成策略 使用native -->
<generator class="native">
</generator>
</id>
<!-- 一些常规属性 -->
<property name="cardNo"></property> <!--谁持有关联,谁就维护关联,所以增加one-to-one标签-->
<one-to-one name="person"></one-to-one> <!--因为是主键关联,不需要指定column-->
</calss>
实体类的关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WoN6ugF1-1630160728334)(F:\LocalTyproPictrue\s22.png)]
数据库的关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYoQL5jn-1630160728336)(F:\LocalTyproPictrue\874710-20161203121004193-814894327-16298981374043.png)]
测试(同上):
Hibernate:
create table t_idcard (
id integer not null auto_increment,
cardNo varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
create table t_person (
id integer not null,
name varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
alter table t_person
add constraint FKn42oi4jn933kff11t0rmgmv6a
foreign key (id)
references t_idcard (id)
Hibernate:
insert
into
t_idcard
(cardNo)
values
(?)
Hibernate:
insert
into
t_person
(name, id)
values
(?, ?)
生成的表并和数据的插入没有变化
加载:
IdCard idCard = session.load(IdCard.class, 1);
System.out.println(idCard.getPerson().getName());
System.out.println(idCard);
Hibernate:
select
idcard0_.id as id1_1_0_,
idcard0_.cardNo as cardno2_1_0_,
person1_.id as id1_2_1_,
person1_.name as name2_2_1_
from
t_idcard idcard0_
left outer join
t_person person1_
on idcard0_.id=person1_.id
where
idcard0_.id=?
杰洛特
IdCard [id=1, Numbers=99999]
成功的根据idCard的主键查出对应的person属性的信息
3.一对一单向外键关联
通过设定的外键(不是主键)来参照其他的表建立关系,一般使用在多对一的关系映射中,
因为他就是多对一的一个特例,如果多端控制为1个的话,那不就是一对一了吗,这里要注意站的角度问题,多对一重点在多端,如果是一对多的话,重点在一端,一端本来就是1了,就没有所谓的特例了,所以还是要到多端去设置让他唯一,这样就达到了一对一关系,因此上面说的是多对一的一个特例,这样解释应该清楚了。如何设置多端唯一呢,通过一个属性 unique=ture。这样就使一个Person对应唯一一个IdCard,因为外键唯一,其他记录无法插入相同的外键值
数据库关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RG8WYe40-1630160728337)(F:\LocalTyproPictrue\s33.png)]
实体对象关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Ukui05x-1630160728338)(F:\LocalTyproPictrue\s44.png)]
Person类和其映射文件hbm.xml
public class Person {
private int id;
private String name;
private IdCard idCard;
....
}
<class name="pojo.Person" table="t_person">
<id name="id" type="int">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="name" type="string"/>
<many-to-one name="idCard" uniqued="true"></many-to-one>
</class>
IdCard类
public class IdCard {
private int id;
private String cardNo;
....
}
<class name="pojo.IdCard" table="t_card">
<id name="id" column="id">
<!-- 主键生成策略 使用native -->
<generator class="native">
</generator>
</id>
<!-- 一些常规属性 -->
<property name="cardNo"></property>
</calss>
测试生成表和记录
Hibernate:
create table t_idcard (
id integer not null auto_increment,
cardNo varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
create table t_person (
id integer not null auto_increment,
name varchar(255),
idCard integer, //生成外键idCard
primary key (id)
) engine=InnoDB
Hibernate:
alter table t_person
add constraint UK_c7ore53eylsbhkt2qt3sf28xl unique (idCard) //添加唯一属性
Hibernate:
alter table t_person
add constraint FKmwhwiyh1l4gnux6mfegyx5ki8
foreign key (idCard) //设置idCard为外键参照t_idcard的主键id
references t_idcard (id)
Hibernate:
insert
into
t_idcard
(cardNo)
values
(?)
Hibernate:
insert
into
t_person
(name, idCard)
values
(?, ?)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lCMdrbKK-1630160728339)(F:\LocalTyproPictrue\sadqweq.png)]
加载:
Person person = session.load(Person.class, 1);
System.out.println(person.getIdCard().getCardNo());
System.out.println(person);
结果:
Hibernate:
select
person0_.id as id1_2_0_,
person0_.name as name2_2_0_,
person0_.idCard as idcard3_2_0_
from
t_person person0_
where
person0_.id=?
Hibernate:
select
idcard0_.id as id1_1_0_,
idcard0_.cardNo as cardno2_1_0_,
person1_.id as id1_2_1_,
person1_.name as name2_2_1_,
person1_.idCard as idcard3_2_1_
from
t_idcard idcard0_
left outer join
t_person person1_
on idcard0_.id=person1_.id
where
idcard0_.id=?
99999
Person [id=1, name=杰洛特, card=IdCard [id=1, Numbers=99999]]
4.一对一双向外键关联
同样只需要修改
IdCard类,然后在其中添加person属性
public class IdCard {
private int id;
private String cardNo;
private Person person;
....
}
<class name="pojo.IdCard" table="t_card">
<id name="id" column="id">
<!-- 主键生成策略 使用native -->
<generator class="native">
</generator>
</id>
<!-- 一些常规属性 -->
<property name="cardNo"></property> <!--谁持有关联,谁就维护关联,所以增加one-to-one标签-->
<!-- 要注意property-ref这个属性,很重要,关键的地方就在这里。
property-ref:指定关联类的属性名,这个属性将会和本类的主键相对应。如果没有指定,
会使用对方关联类的主键来跟本类的主键比较,这里要注意不是关联表中的外键字段名。如果不指定这个属性,那么
一对一默认会使用主键去做对比。相当于原本我们
是可以通过本类的主键去和关联类的外键比较,然后来找到对应记录的,但是这里一对一中没有
column属性,所以该方法行不通,因此就想出了这种办法,不跟外键比,也不能跟主键比(因为不是主键关系),那么
就跟关联表中的一个属性比,也就是我们这个person中的idCard属性,为什么找得到呢,因为从person能找到idCard,那么
person中的idCard中就会有对应的值,我们跟该值比,也就能找到对应的person了。
class:person所在的类,这个也可以不写,hibernate会自动帮我们找到
-->
<one-to-one name="person" property-ref="idCard" class="pojo.Person"/>
</calss>
实体类图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6RiKORpf-1630160728340)(F:\LocalTyproPictrue\double2.png)]
数据库关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3zretRT-1630160728341)(F:\LocalTyproPictrue\s33-16299046543465.png)]
生成表:
Hibernate:
create table t_idcard ( //和单向外键关联的生成一模一样
id integer not null auto_increment,
cardNo varchar(255),
primary key (id)
) engine=InnoDB
Hibernate:
create table t_person (
id integer not null auto_increment,
name varchar(255),
idCard integer,
primary key (id)
) engine=InnoDB
Hibernate:
alter table t_person
add constraint UK_c7ore53eylsbhkt2qt3sf28xl unique (idCard)
Hibernate:
alter table t_person
add constraint FKmwhwiyh1l4gnux6mfegyx5ki8
foreign key (idCard)
references t_idcard (id)
Hibernate:
insert
into
t_idcard
(cardNo)
values
(?)
Hibernate:
insert
into
t_person
(name, idCard)
values
(?, ?)
加载:
Person person = session.load(Person.class, 1);
System.out.println(person.getIdCard().getCardNo());
System.out.println(person);
//---------------------------------------------------------
Hibernate:
select
idcard0_.id as id1_1_0_,
idcard0_.cardNo as cardno2_1_0_,
person1_.id as id1_2_1_,
person1_.name as name2_2_1_,
person1_.idCard as idcard3_2_1_
from
t_idcard idcard0_
left outer join
t_person person1_
on idcard0_.id=person1_.idCard
where
idcard0_.id=?
杰洛特
IdCard [id=1, Numbers=99999]
一对多关系映射
1.多对一单向关联 |
---|
2.一对多单向关联 | 3.多对一双向关联/一对多双向关联 |
一对多,多对一都是使用外键进行关联,故不存在主键关联
1.多对一的单向关联
例:User类关联多个Account,多个Account可以指向同一个User
public class User {
private int id;
private String name;
....
}
<class name="pojo.User" table="t_user">
<id name="id" type="int" column="user_id">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="name" type="string" column="user_name" />
</class>
public class Account {
private int id;
private String anumber;
private User owner;
....
}
<class name="pojo.Account" table="t_account">
<id name="id" type="int" column="a_id">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="anumber" type="string" column="a_number" />
<!--在本类的表中添加参照关联对象owner的类对应的表的主键的外键,命名为userid-->
<many-to-one name="owner" class="pojo.User" column="userid"></many-to-one>
</class>
实体关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kIn0I08O-1630160728342)(F:\LocalTyproPictrue\未命名文件(21)].png)
数据库表关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WqkPPpKA-1630160728343)(F:\LocalTyproPictrue\duo.png)]
生成数据库表和插入数据
create table t_account (
a_id integer not null auto_increment,
a_number varchar(255),
userid integer,
primary key (a_id)
) engine=InnoDB
Hibernate:
create table t_user (
user_id integer not null auto_increment,
user_name varchar(255),
primary key (user_id)
) engine=InnoDB
Hibernate:
alter table t_account
add constraint FKd6w5hyskogyl000dxman01e74
foreign key (userid)
references t_user (user_id)//设置t_account的userid为外键参照t_user的主键
User rich = new User();
rich.setName("理查");
session.save(rich);
Account a1 = new Account();
a1.setAnumber("1111");
a1.setOwner(rich);
Account a2 = new Account();
a2.setAnumber("2222");
a2.setOwner(rich);
session.save(a1);
session.save(a2);
Hibernate:
insert
into
t_user
(user_name) //为了持久化对象拿到ID发的insert语句,没有意义
values
(?)
Hibernate:
insert
into
t_account
(a_number, userid) //因为account持有关系,所以account维护关系,
//将外键userid设置为关联对象(owner)的主键id
values
(?, ?)
Hibernate:
insert
into
t_account
(a_number, userid)
values
(?, ?)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pP6bRQWF-1630160728344)(F:\LocalTyproPictrue\j11.png)]
加载:
Account a1 = session.load(Account.class, 1);
System.out.println(a1.getOwner().getName());
System.out.println(a1);
Hibernate:
select
account0_.a_id as a_id1_0_0_,
account0_.a_number as a_number2_0_0_,
account0_.userid as userid3_0_0_
from
t_account account0_
where
account0_.a_id=?
Hibernate:
select
user0_.user_id as user_id1_3_0_,
user0_.user_name as user_name2_3_0_
from
t_user user0_
where
user0_.user_id=?
理查
Account [id=1, anumber=1111, owner=User [id=1, name=理查]]
2.一对多单的向关联
根据谁持有关系,谁就维护对象关系的原则,
上例中的单向多对一中的Account持有User的关联,所以就由多的一方Account维护关系,
而单向一对多则是多的一方没有关联,单的一方持有关联,如下
public class User {
private int id;
private String name;
private Set<Account> accounts;
...
}
<class name="pojo.User" table="t_user">
<id name="id" type="int" column="user_id">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="name" type="string" column="user_name" />
<!--关联属性-->
<set name="accounts">
<!--给关联对象的类的表添加名为userid的外键约束-->
<key column="userid" />
<!--指定set集合中的类型为关联的对象的类型-->
<one-to-many class="pojo.Account"></one-to-many>
</set>
</class>
public class Account {
private int id;
private String anumber;
....
}
<!--映射一个普通实体类-->
<class name="pojo.Account" table="t_account">
<id name="id" type="int" column="a_id">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="anumber" type="string" column="a_number" />
</class>
数据库关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vqaQ3zr9-1630160728345)(F:\LocalTyproPictrue\onetomany.png)]
实体类关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vmeTVZD4-1630160728345)(F:\LocalTyproPictrue\qis.png)]
生成表的语句
Hibernate:
create table t_account (
a_id integer not null auto_increment,
a_number varchar(255),
userid integer, //t_user表向t_account表中插入了userid作为外键参照t_user的主键
primary key (a_id) //但Account类并不知道自己的表中有userid字段
) engine=InnoDB
Hibernate:
create table t_user (
user_id integer not null auto_increment,
user_name varchar(255),
primary key (user_id)
) engine=InnoDB
Hibernate:
alter table t_account
add constraint FKd6w5hyskogyl000dxman01e74 //自动添加外键约束
foreign key (userid)
references t_user (user_id)
插入相关数据:
User rich = new User();
rich.setName("理查");
session.save(rich);
Account a1 = new Account();
a1.setAnumber("1111");
Account a2 = new Account();
a2.setAnumber("2222");
session.save(a1);
session.save(a2);
Set<Account> accounts = new HashSet<>();
accounts.add(a1);
accounts.add(a2);
rich.setAccounts(accounts);
产生的sql语句:
Hibernate:
insert
into
t_user
(user_name)
values
(?) //为了设置ID主键发的insert语句,没有意义
Hibernate:
insert
into
t_account
(a_number)
values //为了设置ID主键发的insert语句,没有意义,
(?) //因为Account并不持有和维护关联,所以不会去设置那个外键userid的值,此时userid字段为null
Hibernate:
insert
into
t_account
(a_number)
values //同上
(?)
Hibernate:
update
t_account //将account集合添加并commit后,User需要维护关系,将set集合类内的对象对应的表的外键(userid)
set //设置为t_user表的主键值,所以发起update的语句,将尚为null值的userid设置为t_user的主键值
userid=? //因此是主键主动关联外键,查询时自然也是主键查找外键(通过t_user的id查找t_account的userid)
where
a_id=?
Hibernate:
update
t_account
set
userid=?
where
a_id=?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dy5WbCTv-1630160728346)(F:\LocalTyproPictrue\aw1-16299812980832.png)]
一对多单向关联的缺点
一.向关联表设置外键而关联类并不知道,每有一个关联类对象需要维护关系(getAccounts().add(Account对象)),就需要发一条update语句,增加了开销
二.若userid(外键)的属性为not null,则根本无法持久化account对象(无法插入t_aacount表)
3.一对多/多对一双向关联
在双向关联中多对一和一对多是相互的,因为双方都有关联,一方是一对多,多方是多对一,所以双向多对一和一对多是没有区别的。
有因为双方都持有关联,所以双方都要维护关系
public class User {
private int id;
private String name;
private Set<Account> accounts;
...
}
<class name="pojo.User" table="t_user">
<id name="id" type="int" column="user_id">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="name" type="string" column="user_name" />
<set name="accounts">
<key column="userid"></key> <!--向关联类型Account类对应的表中设置名为userid的外键-->
<one-to-many class="pojo.Account"/>
</set>
</class>
public class Account {
private int id;
private String anumber;
private User owner;
..
}
<class name="pojo.Account" table="t_account">
<id name="id" type="int" column="a_id">
<!-- 主键生成策略 -->
<generator class="native">
</generator>
</id>
<!-- 实体类的属性 -->
<property name="anumber" type="string" column="a_number" />
<!--在本类对应的表中添加外键参照于关联对象owner对应的类(User)对应的表的主键(user_id)-->
<!--column属性的值必须和一方关系set标签中的key标签的name属性相同,表示两个类设置的是同一个外键-->
<!--如果column属性和对方key标签的name属性不同,则会设置两个外键(名字分别和column属性,key的name属性相同)-->
<many-to-one name="owner" class="pojo.User" column="userid"></many-to-one>
</class>
实体类关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQ3xPoNW-1630160728350)(F:\LocalTyproPictrue\dou.png)]
在实体类中双方都能够查到关联类的信息,具备了双向关联
数据库关系:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4TE19pTD-1630160728351)(F:\LocalTyproPictrue\d11.png)]
数据库的表并没有改变,只是双方对象都能够通过userid这个外键来维护关系
生成表的语句:
User rich = new User();
rich.setName("理查");
session.save(rich);
Account a1 = new Account();
a1.setAnumber("1111");
a1.setOwner(rich);
Account a2 = new Account();
a2.setAnumber("2222");
a2.setOwner(rich);
session.save(a1);
session.save(a2);
Set<Account> accounts = new HashSet<>();
accounts.add(a1);
accounts.add(a2);
rich.setAccounts(accounts);
Hibernate:
create table t_account (
a_id integer not null auto_increment,
a_number varchar(255),
userid integer,
primary key (a_id)
) engine=InnoDB
Hibernate:
create table t_user (
user_id integer not null auto_increment,
user_name varchar(255),
primary key (user_id)
) engine=InnoDB
Hibernate:
alter table t_account
add constraint FKd6w5hyskogyl000dxman01e74 //设置userid为外键操作t_user的user_id
foreign key (userid)
references t_user (user_id)
Hibernate:
insert
into
t_user
(user_name) //持久化user为了拿到ID发的insert语句,没有意义
values
(?)
Hibernate:
insert
into
t_account
(a_number, userid) //持久化account对象时,因为设置了关联对象,account为了维护关系插入了外键userid
values
(?, ?)
Hibernate:
insert
into
t_account
(a_number, userid) //持久化account对象时,因为设置了关联对象,account为了维护关系插入了外键userid
values
(?, ?)
Hibernate:
update
t_account //commit提交后,因为设置了关联对象(setAccounts(Set<Account>)),user也要根据
set //set集合内的关联对象(account对象)的类对应的表(t_account)的外键(userid)来关联
userid=? //t_user的user_id主键,即user也要维护关系,将account对象的外键设置为user的主键,
where //故再次发出了两条update语句,但在之前account已经维护了关系(setOwner(user)),向 //t_account中插入了userid,所以这两条update语句是多余的
a_id=?
Hibernate:
update
t_account
set
userid=?
where
a_id=?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WHctnRLb-1630160728352)(F:\LocalTyproPictrue\aw1-16299882575814.png)]
测试:
User user = session.load(User.class, 1);
Account a1 = session.load(Account.class, 1);
Set<Account> announces = user.getAccounts();
announces.forEach(announce->System.out.println(announce));
System.out.println(a1.getOwner().getName());
结果:
Hibernate:
select
user0_.user_id as user_id1_3_0_,
user0_.user_name as user_nam2_3_0_
from
t_user user0_
where
user0_.user_id=?
Hibernate:
select
accounts0_.userid as userid3_0_0_,
accounts0_.a_id as a_id1_0_0_,
accounts0_.a_id as a_id1_0_1_,
accounts0_.a_number as a_number2_0_1_,
accounts0_.userid as userid3_0_1_
from
t_account accounts0_
where
accounts0_.userid=?
Account [id=2, anumber=2222]
Account [id=1, anumber=1111]
理查
4.inverse和cascade的使用
在双向的一对多/多对一和双向的多对多的关系中,双方都持有关联属性的关系,就像上例中的User和account,
更具谁持有关系,谁就维护关系的原则,双方都要维护关系,
account一方维护了关系,向外建useid插入关联类的id,
user也维护了关系,更新set中关联属性对象的外键为本类对象的主键的值
这也就造成了后面的那两条多余的update语句
虽然没有出现错误,但显然降低了效率
使用inverse属性来指定哪一方来维护关联关系
inverse的默认为false
inverse只存在于集合标记的元素中 。
Hibernate提供的集合元素包括
inverse=“true”,表示这一方放弃维护关系,由另一方来维护关系
例:将双向一对多/多对一关联的1的一方设置inverse=“true”,将关系交给多的一方维护,只需将上例稍微改造,代码如下
<class name="pojo.User" table="t_user">
<id name="id" type="int" column="user_id">
<generator class="native">
</generator>
</id>
<property name="name" type="string" column="user_name" />
<set name="accounts" inverse="true">
<key column="userid"></key>
<one-to-many class="pojo.Account"/>
</set>
</class>
将表删除,重新插入数据,生成的sql语句如下
User rich = new User();
rich.setName("理查");
session.save(rich);
Account a1 = new Account();
a1.setAnumber("1111");
a1.setOwner(rich);
Account a2 = new Account();
a2.setAnumber("2222");
a2.setOwner(rich);
session.save(a1);
session.save(a2);
Set<Account> accounts = new HashSet<>();
accounts.add(a1);
accounts.add(a2);
rich.setAccounts(accounts); //插入数据的代码没变,但是却少了那两条update语句
//说明user对象没有根据设置关联对象去维护关系,
//这就是inverse的作用
//---------------------------------------------
Hibernate:
insert
into
t_user
(user_name)
values
(?)
Hibernate:
insert
into
t_account
(a_number, userid)
values
(?, ?)
Hibernate:
insert
into
t_account
(a_number, userid)
values
(?, ?)
由于user并不维护关系,所以只需要通过account(多的一方)来维护关系即可,可以将插入数据的代码改为,删表重新运行
User rich = new User();
rich.setName("理查");
session.save(rich);
Account a1 = new Account();
a1.setAnumber("1111");
a1.setOwner(rich);
Account a2 = new Account();
a2.setAnumber("2222");
a2.setOwner(rich);
session.save(a1);
session.save(a2);
//Set<Account> accounts = new HashSet<>();
//accounts.add(a1);
//accounts.add(a2);
//rich.setAccounts(accounts);
结果为:
Hibernate:
create table t_account (
a_id integer not null auto_increment,
a_number varchar(255),
userid integer,
primary key (a_id)
) engine=InnoDB
Hibernate:
create table t_user (
user_id integer not null auto_increment,
user_name varchar(255),
primary key (user_id)
) engine=InnoDB
Hibernate:
alter table t_account
add constraint FKd6w5hyskogyl000dxman01e74
foreign key (userid)
references t_user (user_id)
Hibernate:
insert
into
t_user
(user_name)
values
(?)
Hibernate:
insert
into
t_account
(a_number, userid)
values
(?, ?)
Hibernate:
insert
into
t_account
(a_number, userid)
values
(?, ?)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E4F0ytRx-1630160728352)(F:\LocalTyproPictrue\aw1-16299935617955.png)]
依然一样,所以确定user并没有维护关系,
这样一来,就像多对一单向关联一样,只需要多的一方维护关系就可以了,
但单向关联user是不可以查到account的,
来实验以下看看user在inverse="true"的情况是是否能够查到accounts集合
在相同的测试代码中,结果也是一样
User user = session.load(User.class, 1);
Account a1 = session.load(Account.class, 1);
Set<Account> announces = user.getAccounts();
announces.forEach(announce->System.out.println(announce));
System.out.println(a1.getOwner().getName());
//------------------------------------------------------------
Hibernate:
select
user0_.user_id as user_id1_3_0_,
user0_.user_name as user_nam2_3_0_ //查询t_user表的信息
from
t_user user0_
where
user0_.user_id=?
Hibernate:
select
accounts0_.userid as userid3_0_0_,
accounts0_.a_id as a_id1_0_0_,
accounts0_.a_id as a_id1_0_1_,
accounts0_.a_number as a_number2_0_1_,
accounts0_.userid as userid3_0_1_
from
t_account accounts0_
where
accounts0_.userid=? //更具user的id去account表中查找外键(userid)与user_id相同的account信息
Account [id=2, anumber=2222]
Account [id=1, anumber=1111]
理查
所以只要由一方来维护关系就可以达到双向关联了,
又因为一对多中由1方来维护关系的效率不高,所以在大多数的双向多对一/一对多的关联中应该让1方放弃维护关系(设置inverse=“true”),而由多的一方来维护关系
cascade
cascade属性的作用是描述关联对象进行操作时的级联特性。因此,只有涉及到关系的元素才有cascade属性。
具 有cascade属性的标记包括
注意:和 是用在集合标记内部的,所以是不需要cascade属性的。
级联操作:指当主控方执行某项操作时,是否要对被关联方也执行相同的操作。
如上例的account类的owner属性在hbm.xml中添加上级联属性,
<class name="pojo.Account" table="t_account">
<id name="id" type="int" column="a_id">
<generator class="native">
</generator>
</id>
<property name="anumber" type="string" column="a_number" />
<many-to-one name="owner" class="pojo.User" column="userid" cascade="all"></many-to-one>
</class>
User rich = new User();
rich.setName("理查");
Account a1 = new Account();
a1.setAnumber("1111");
a1.setOwner(rich);
Account a2 = new Account();
a2.setAnumber("2222");
a2.setOwner(rich);
session.save(a1);
session.save(a2);
结果:
Hibernate:
insert
into
t_user
(user_name) //并没有通过Session去持久化user对象,但数据库依然插入了user的记录
values //如果没有cascade属性,一定会出现瞬时态对象异常TrainsacObjectException
//因为它并没有纳入session缓存(没有ID属性)
(?) //所以cascade="all"一定调用save方法持久化了user对象
Hibernate: //由此在持久化account对象时,cascade检查了其关联的user对象,并将其也持久化
insert
into
t_account
(a_number, userid)
values
(?, ?)
Hibernate:
insert
into
t_account
(a_number, userid)
values
(?, ?)
cascade的可选值
1.all: 所有情况下均进行关联操作,即save-update和delete。 |
---|
2.none: 所有情况下均不进行关联操作。这是默认值。 | 3.save-update: 在执行save/update/saveOrUpdate时进行关联操作。 | 4.all-delete-orphan: 当一个节点在对象图中成为孤儿节点时,删除该节点 |
?
其他应该度知道,说一下这个all-delete-orphan:什么是孤儿节点,举个例子,班级和学生,一张classes表,一张student表,student表中有5个学生的数据,其5个学生都属于这个班级,也就是这5个学生中的外键字段都指向那个班级,现在删除其中一个学生(remove),进行的数据操作仅仅是将student表中的该学生的外键字段置为null,也就是说,则个学生是没有班级的,所以称该学生为孤儿节点,我们本应该要将他完全删除的,但是结果并不如我们所想的那样,所以设置这个级联属性,就是为了删除这个孤儿节点。也就是解决这类情况。
多对多关系映射
1.多对多单向关联
以经典的学生选课为例:
public class Student {
private int id;
private String name;
private boolean sex;
private Set<Course> courses;
...
}
<class name="pojo.Student" table="t_student">
<id name="id" column="student_id">
<generator class="native"></generator>
</id>
<property name="name" column="student_name"></property>
<property name="sex" column="student_sex"></property>
<!--维护多对多的关系,指定中间表-->
<set name="courses" table="t_student_course">
<!--设置中间表的外键关联本类的主键(s_id)-->
<key column="s_id"></key>
<!--多对多标签,指定关联类在中间表的外键名称(c_id)-->
<many-to-many class="pojo.Course" column="c_id"></many-to-many>
</set>
</class>
public class Course {
private int id;
private String name;
...
}
<class name="pojo.Course" table="t_course">
<!--普通类的映射-->
<id name="id" column="Course_id">
<generator class="native"></generator>
</id>
<property name="name" column="Course_name"></property>
</class>
数据库关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9RGiMqm-1630160728353)(F:\LocalTyproPictrue\llc.png)]
实体类关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BAK0hCNx-1630160728354)(F:\LocalTyproPictrue\ops.png)]
生成表并插入数据:
Student s1 = new Student();
s1.setName("张三");
s1.setSex(true);
Student s2 = new Student();
s2.setName("李四");
s2.setSex(true);
Course c1 = new Course();
c1.setName("语文");
Course c2 = new Course();
c2.setName("数学");
Course c3 = new Course();
c3.setName("英语");
session.save(s1);
session.save(s2);
session.save(c1);session.save(c2);session.save(c3);
Set<Course> set1 = new HashSet<>();set1.add(c1);set1.add(c2);
Set<Course> set2 = new HashSet<>();set2.add(c1);set2.add(c2);set2.add(c3);
s1.setCourses(set1);
s2.setCourses(set2);
Hibernate:
create table t_course (
Course_id integer not null auto_increment, //课程表
Course_name varchar(255),
primary key (Course_id)
) engine=InnoDB
Hibernate:
create table t_student (
student_id integer not null auto_increment, //学生表
student_name varchar(255),
student_sex bit,
primary key (student_id)
) engine=InnoDB
Hibernate:
create table t_student_course ( //中间表
s_id integer not null,
c_id integer not null,
primary key (s_id, c_id) //设置联合主键
) engine=InnoDB
Hibernate:
alter table t_student_course
add constraint FK4ofi7wwyawdxj17rva1qlrqn //添加外键关联
foreign key (c_id)
references t_course (Course_id)
Hibernate:
alter table t_student_course //添加外键关联
add constraint FKbphe0lu2o1xuves08sgj835ka
foreign key (s_id)
references t_student (student_id)
Hibernate:
insert
into
t_student
(student_name, student_sex) //持久化,生成学生对象的ID
values
(?, ?)
Hibernate:
insert
into
t_student
(student_name, student_sex) //持久化,生成学生对象的ID
values
(?, ?)
Hibernate:
insert
into
t_course
(Course_name) //持久化,生成课程对象的ID
values
(?)
Hibernate:
insert
into
t_course
(Course_name) //持久化,生成课程对象的ID
values
(?)
Hibernate:
insert
into
t_course
(Course_name) //持久化,生成课程对象的ID
values
(?)
//--------------------------------------------------------分割线,下面是student维护关系对中间表插入了5条数据
Hibernate:
insert
into
t_student_course //设置多对多的关联信息,向关联表t_student_cource插入数据
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bGfjqXA-1630160728355)(F:\LocalTyproPictrue\t33.png)]
加载:
Student s1 = session.load(Student.class, 1);
Set<Course> courses = s1.getCourses();
courses.forEach(course->{System.out.println(course);});
System.out.println(s1.getName()+s1.getId());
Hibernate:
select
student0_.student_id as student_1_4_0_,
student0_.student_name as student_2_4_0_,
student0_.student_sex as student_3_4_0_
from
t_student student0_
where
student0_.student_id=?
Hibernate:
select
courses0_.s_id as s_id1_5_0_,
courses0_.c_id as c_id2_5_0_,
course1_.Course_id as course_i1_1_1_,
course1_.Course_name as course_n2_1_1_
from
t_student_course courses0_
inner join
t_course course1_
on courses0_.c_id=course1_.Course_id
where
courses0_.s_id=?
Course [id=1, name=语文]
Course [id=2, name=数学]
张三1
2.多对多双向关联
无非是在Cource课程类中也可以查到Student类的信息
public class Course {
private int id;
private String name;
private Set<Student> students;
...
}
<class name="pojo.Course" table="t_course">
<id name="id" column="Course_id">
<generator class="native"></generator>
</id>
<property name="name" column="Course_name"></property>
<set name="students" table="t_student_cource">
<key column="c_id"></key>
<many-to-many class="pojo.Student" column="s_id"></many-to-many>
</set>
</class>
数据库关系图不变,
实体类关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOQxtUGJ-1630160728356)(F:\LocalTyproPictrue\aos1.png)]
重新创建表并插入语句:
System.out.println("多对多测试");
Student s1 = new Student();
s1.setName("张三");
s1.setSex(true);
Student s2 = new Student();
s2.setName("李四");
s2.setSex(true);
Course c1 = new Course();
c1.setName("语文");
Course c2 = new Course();
c2.setName("数学");
Course c3 = new Course();
c3.setName("英语");
session.save(s1);
session.save(s2);
session.save(c1);session.save(c2);session.save(c3);
Set<Course> set1 = new HashSet<>();set1.add(c1);set1.add(c2);
Set<Course> set2 = new HashSet<>();set2.add(c1);set2.add(c2);set2.add(c3);
s1.setCourses(set1);
s2.setCourses(set2);
结果:
出现异常
MySQL:Duplicate entry '1-2' for key 'PRIMARY' 错误
这是因为在默认情况下n-n的两端都会维护关联关系,当执行上述代码后,student要维护关联关系,往连接表中添加5条记录,然后cource也要维护关联关系,往连接表中添加相同的5条记录, 会导致连接表中主键重复,
结论:
双向多对多关联无法在双方都维护关系的情况下设置双向关联
当删除上一个代码块中的注释语句后,程序正常运行。
结果如下:
Hibernate:
create table t_course (
Course_id integer not null auto_increment,
Course_name varchar(255),
primary key (Course_id)
) engine=InnoDB
Hibernate:
create table t_student (
student_id integer not null auto_increment,
student_name varchar(255),
student_sex bit,
primary key (student_id)
) engine=InnoDB
Hibernate:
create table t_student_course (
s_id integer not null,
c_id integer not null,
primary key (c_id, s_id)
) engine=InnoDB
Hibernate:
alter table t_student_course
add constraint FK4ofi7wwyawdxj17rva1qlrqn
foreign key (c_id)
references t_course (Course_id)
Hibernate:
alter table t_student_course
add constraint FKbphe0lu2o1xuves08sgj835ka
foreign key (s_id)
references t_student (student_id)
八月 28, 2021 9:36:25 下午 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
Hibernate:
insert
into
t_student
(student_name, student_sex)
values
(?, ?)
Hibernate:
insert
into
t_student
(student_name, student_sex)
values
(?, ?)
Hibernate:
insert
into
t_course
(Course_name)
values
(?)
Hibernate:
insert
into
t_course
(Course_name)
values
(?)
Hibernate:
insert
into
t_course
(Course_name)
values
(?)
//--------------------------------------------------------分割线,下面是student维护关系对中间表插入了5条数据
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
Hibernate:
insert
into
t_student_course
(s_id, c_id)
values
(?, ?)
加载:
Course c1 = session.get(Course.class, 1);
Set<Student> students = c1.getStudents();
students.forEach(student->{System.out.println(student.getId()+student.getName()+student.isSex());});
System.out.println(c1.getName());
Hibernate:
select
course0_.Course_id as course_i1_1_0_,
course0_.Course_name as course_n2_1_0_
from
t_course course0_
where
course0_.Course_id=?
Hibernate:
select
students0_.c_id as c_id2_5_0_,
students0_.s_id as s_id1_5_0_,
student1_.student_id as student_1_4_1_,
student1_.student_name as student_2_4_1_,
student1_.student_sex as student_3_4_1_
from
t_student_course students0_
inner join
t_student student1_
on students0_.s_id=student1_.student_id
where
students0_.c_id=?
1张三true
2李四true
语文
正确的查出了数据
建议:
既然双向多对多无法同时由双方一起维护关系,
那么就应该设置任意一方的关联属性为inverse=“true”,使操作类似于单向多对多
感想:
将关系映射,根据方向,类型进行分类更有利于理解和应用Hibernate的相关技术
_course (Course_name) values (?) Hibernate: insert into t_course (Course_name) values (?) //--------------------------------------------------------分割线,下面是student维护关系对中间表插入了5条数据 Hibernate: insert into t_student_course (s_id, c_id) values (?, ?) Hibernate: insert into t_student_course (s_id, c_id) values (?, ?) Hibernate: insert into t_student_course (s_id, c_id) values (?, ?) Hibernate: insert into t_student_course (s_id, c_id) values (?, ?) Hibernate: insert into t_student_course (s_id, c_id) values (?, ?)
加载:
```java
Course c1 = session.get(Course.class, 1);
Set<Student> students = c1.getStudents();
students.forEach(student->{System.out.println(student.getId()+student.getName()+student.isSex());});
System.out.println(c1.getName());
//----------------------------------------------
Hibernate:
select
course0_.Course_id as course_i1_1_0_,
course0_.Course_name as course_n2_1_0_
from
t_course course0_
where
course0_.Course_id=?
Hibernate:
select
students0_.c_id as c_id2_5_0_,
students0_.s_id as s_id1_5_0_,
student1_.student_id as student_1_4_1_,
student1_.student_name as student_2_4_1_,
student1_.student_sex as student_3_4_1_
from
t_student_course students0_
inner join
t_student student1_
on students0_.s_id=student1_.student_id
where
students0_.c_id=?
1张三true
2李四true
语文
正确的查出了数据
建议:
既然双向多对多无法同时由双方一起维护关系,
那么就应该设置任意一方的关联属性为inverse=“true”,使操作类似于单向多对多
感想:
将关系映射,根据方向,类型进行分类更有利于理解和应用Hibernate的相关技术
|