之前我学习了单向的关联,现在继续学习,双向的关联。这个关联关系的理解还是有点复杂,慢慢的理解懂了就好啦!这个过程不是一蹴而就的。我们需要不断的积累,才可以有所成绩的。
年轻人,不要怕~慢慢来
对啦,有的时候我们可能会采用逆向工程产生实体哦。所以我们要看得懂,会修改,会改变
双向1-N关联对于1-N的关联,Hibernate 推荐使用双向的关联,而不要让1的端(也就是有Set这个集合的那一个)控制关联的关系。而是使用N的一端控制关联关系。双向的1-N和N-1关联其实就是一回事。两端都需要增加对关联属性的访问,N的一端增加引用关联实体的属性,1的一端增加集合属性,集合元素为关联的实体.
Hibernate 同样对月双向关联的映射提供了两种技术支持:一种是有连接表的,一种是无连接表的。对于大部分来说,对于1-N的双向关联我们采用无连接表的策略即可。
无连接的双向的1-N关联
无连接的双向的1-N关联,N的一端肯定是使用@ManyToOne 我们肯定不会默生的涩,1的一端使用@OneToMany.
底层的数据库在记录这种1-N的关联关系的时候,其实就是我们的外键而已,只需要在N的一端增加一个外键列就好啦。因此在使用@ManyToOne的同时,还需要使用@JointColumn来映射外键列。
对于双向的1-N关联的映射,通常不会让1(set集合)的端控制关系,这样没次都需要更新我们的set集合,很不方便的说。而是该由我们的N的一端控制关联的关系。因此我们使用@OneToMany的时候中应该指明mappedBy (映射由谁控制) mappedBy属性—–一但为@OneToMany,@ManyToMany指定了这个属性,则表明当前的实体不能控制关联的关系。放弃控制关系之后,就不能指明@JoinColumn 或@JointTable 来修饰关联实体的属性啦(这两个实体都是外键和第三张表啊来维护关系)
例子:下面的@OneToMany在关联实体的set集合,指明了mappedBy那么1 的端放弃控制关联关系,其实这样也好。每次都更新set集合好累啊。
这个所谓的维护关联关系,主要是看底层的表中是否有外键,如果有外键的话,肯定的先有外键生成,才可以产生现在的嘛。不能乱了顺序涩。
比如 person这个一的端 现在不维护关联关系了 这么说嘛person的 底层表中没得关于地址的任何消息所以我可以随便的保存person实体。而不需要任何的条件。 但是address就不行了,因为现在表中有了person_id这个外键。,所以必须等到有了person实体的属性,才可以让我的地址产生并且维护他们之间的关系。 后面有例子的。这个只是我自己的看法。以后忘了再来看看当时的心德。
例子~~
Person 1个人 多个地址
@Entity
@Table(name="person_inf")
public class Person
{
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定义该Person实体所有关联的Address实体
// 指定mappedBy属性表明该Person实体不控制关联关系
//这里的person是address中的person
@OneToMany(targetEntity=Address.class
, mappedBy="person")
private Set<Address> addresses
= new HashSet<>();
...
}
然后Address中只需要增加一个Person person这个属性。需要使用@ManyToOne @JoinColumn 设置外键
继续看例子
@Entity
@Table(name="address_inf")
public class Address
{
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
private String addressDetail;
// 定义该Address实体关联的Person实体
@ManyToOne(targetEntity=Person.class)
// 定义名为person_id外键列,该外键列引用person_inf表的person_id列。
@JoinColumn(name="person_id" , referencedColumnName="person_id"
, nullable=false)
private Person person;
...
}
不知道你是不是觉得好神奇啊,我们的底层的数据库都是一样的都是增加了外键,只是在person中增加了个set然后呢,我们就双向关联了,任意的获得对方的属性啦。开源的力量特别叼
底层数据和我们的1-N的一样的哦~我是觉得很厉害~不知道你认为呢
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();
// 创建一个瞬态的Person对象
Person p = new Person();
p.setName("Test");
p.setAge(29);
你说现在我可以保存到数据库? Person中的set没有赋值哦。可能你忘记啦,我们表的关系是有address维护的,在address中有个外键咯。所以现在我们的person表和address没有关系。我的person当然可以持久化啦。这个 毫无疑问
// 持久化Person对象(对应于插入主表记录)
session.save(p);
没问题
创建address啦
Address a = new Address("遵义市海龙坝");
现在可以保存?当然不可以,我们必须有外键呢,外键都没得,所以得设置关联关系。并且我们还必须是个持久化的对象,不能是瞬态的对象,上面的person是个持久态的,所以可以。为啥瞬态的不行呢?因为我们没有设置级联关系,插入的时候不会级联的操作,这样的话,外键表Id不存在,会报错的出现异常。你懂了?我也是慢慢的自己分析懂啦
// 先设置Person和Address之间的关联关系
a.setPerson(p);
// 再持久化Address对象(对应于插入从表记录)
session.persist(a); //a 作为外键必须是持久化对象,不然会报错的
tx.commit();
HibernateUtil.closeSession();
你懂了吧?这个理解很重要哦,Hibernate的难点,持久化对象,关联关系。记得大二的时候觉得框架好特别,没得人带我飞,自己看这个Hibernate,看的那个各种晕,现在好辣,终于可以懂啦。不错,说明在进步。
有连接表的双向1-N关联我们的关注底层数据库以怎么样的形式呈现给我们呢,因为增加第三张表来维护关系。Person和Address表中都没有外键啦!这样子不需要担心保存我们的Person 信息和Address 信息。只是在建立两者之间的关联关系的时候,放在第三张表中,要保证我们的外键有效,什么是有效呢?address.setPonsen(b) 这种情况下要保证adress 和 b都是有效的,数据库中存在的,才会插入第三张表中没得问题。
对于有连接表的1-N,让N端控制关系的话,1的端无需改变,N的端和单向有连接表的类似
@Id
@Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
private String addressDetail;
// 定义该Address实体关联的Person实体
@ManyToOne(targetEntity=Person.class)
// 映射连接表,指定连接表为person_address
@JoinTable(name="person_address",
// 指定连接表中address_id列参照当前实体对应数据表的address_id主键列
joinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id", unique=true),
// 指定连接表中person_id列参照当前实体的关联实体对应数据表的person_id主键列
inverseJoinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id")
)
private Person person;
...
}
person –我们还是不维持关系吧。其实吧,这里谁维持关系都一样。都是插入第三张表中,性能没得影响。person中也可以使用@JoinTableb 表名一样就行啦。不需要指定谁维护,都一样啊!
因为是第三张表中的哈哈。
@Entity
@Table(name="person_inf")
public class Person
{
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定义该Person实体所有关联的Address实体
@OneToMany(targetEntity=Address.class ,mappedBy="person")
这两种的区别就是把,维护关系不啊~
/*@OneToMany(targetEntity=Address.class)
// 映射连接表,指定连接表为person_address
@JoinTable(name="person_address",
// 指定连接表中person_id列参照当前实体对应数据表的person_id主键列
joinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id"),
// 指定连接表中address_id列参照当前实体的关联实体对应数据表的address_id主键列
inverseJoinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id", unique=true)
)*/
private Set<Address> addresses
= new HashSet<>();
看上面的两种关系都可以,维护维护关系影响不大的,自己看着办就是啦。因为第三张表啦。对于性能没影响,不是更新,是插入第三张表。两者都可以维护关系
看你理解没理解,不需要维护关系是啥子意思呢?
我们来操作就知道啦
// 创建一个瞬态的Person对象
Person p = new Person();
p.setName("Test");
p.setAge(21);
// 创建一个瞬态的Address对象
Address a = new Address("遵义市海龙坝");
现在可以a.setPerson? 答案不可以的,第三张表中有两个外键,作为主键,引用了我们的Person和Address .现在的Person和address对象都不是持久化对象在数据库中不存在。不行吧??
session.save(p);
session.save(a)
a.setPerson(p)
.commit()...
这样子就行啦
下面的也是可以的
a.setPerson(p);
// 持久化Address对象
session.persist(a);
// 持久化Person对象
session.save(p);
tx.commit();
为啥子 可以呢,persist并不是立即转换为insert语句的,save是立即的。所以这样子就好像可以啦~两个外键都存在。
双向的N-N关联双向的N-N 两端都需要set集合,两端都需要增加对Set集合的属性的访问,这里的Set集合是关联关系的,不要和以前的不是关联关系的搞混了。双向的有太多的可能,所以只能采用连接表来操作啦。来表示实体之间的关系。
双向的N-N关联,两端都要set分别使用@ManyToMany 都要使用@JoinTable 显示的连接在一起,和刚刚一对N不同的是(1-N增加了unique约束)
需要说明的是,哪端想放弃控制关联关系,可以使用mappBy。也就不能使用@joinTable啦。其实吧上面都是说过了的。建立第三张表啦。我们两端都可以随意的控制关联关系。重点在于底层的数据库怎么实现的。理解清楚啦,你就知道现在可以持久化?没有控制关系诶!
来来例子,虽然都是差不多,但是还是来啊~
@Entity
@Table(name="person_inf")
public class Person
{
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
@ManyToMany(targetEntity=Address.class)
// 映射连接表,指定连接表的表名为person_address
@JoinTable(name="person_address",
// 映射连接表中名为person_id的外键列,
// 该列参照当前实体对应表的person_id主键列
joinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id"),
// 映射连接表中名为address_id的外键列,
// 该列参数当前实体的关联实体对应表的address_id主键列
inverseJoinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id")
)
private Set<Address> addresses
= new HashSet<>();
一样的继续
@Entity
@Table(name="address_inf")
public class Address
{
// 标识属性
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
// 定义地址详细信息的成员变量
private String addressDetail;
// 定义该Address实体所有关联的Person实体
@ManyToMany(targetEntity=Person.class)
// 映射连接表,指定连接表的表名为person_address
@JoinTable(name="person_address",
// 映射连接表中名为address_id的外键列,
// 该列参照当前实体对应表的address_id主键列
joinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id"),
// 映射连接表中名为person_id的外键列,
// 该列参照当前实体对应表的person_id主键列
inverseJoinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id")
)
private Set<Person> persons
= new HashSet<>();
...
}
两面都没放弃控制权,哈哈。没必要!就是晓得原理啦,随意来啊~~
我就是任性
// 创建一个Person对象
Person p = new Person();
// 持久化Person对象(对应于插入主表记录)
session.save(p);
// 创建一个瞬态的Address对象
Address a = new Address("遵义市海龙坝");
// 先设置Person和Address之间的关联关系
a.getPersons().add(p);
// 再持久化Address对象
session.persist(a);
这上面没错的,我先持久化了person,创建关联关系。在持久化地址。地值在持久化的时候,会先创建插入地址,在插入到关系表中的。
也可以这样写
session.save(a);
a.getPersons().add(p);
这样也是可以的,最后提交的时候就插入到了数据库中。多自己分析哈,自然的就懂了。
基于外键的哦
直接的代码。前面的懂了,这点还不是小意思。你维护不维护实体关系呢?一方维护,一方不维护涩
总有一方要先把实体存到数据库中,然后另一方才有外键存在呢。
有mappedBy的不维护,那么低层的数据表中就不会出现啦,所以,可以自己创建并保存到数据库,不用去理会这个属性。
@Entity
@Table(name="person_inf")
public class Person
{
// 标识属性
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定义该Person实体关联的Address实体
@OneToOne(targetEntity=Address.class , mappedBy="person")
private Address address;
...
}
所以下面
Person p=new Person();
session.save(p)没有压力
…
继续地址,这里没有使用连接表哦。还是有先后顺序的。必须先有person在有address;
@Entity
@Table(name="address_inf")
public class Address
{
// 标识属性
@Id @Column(name="address_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int addressId;
// 定义地址详细信息的成员变量
private String addressDetail;
// 定义该Address实体关联的Person实体
@OneToOne(targetEntity=Person.class)
// 用于映射person_id外键列,参照person_inf表的person_id列
// 指定了unique=true表明是1-1关联
@JoinColumn(name="person_id" , referencedColumnName="person_id"
, unique=true)
private Person person;
..
}
地层的数据库中多了个person_id 外键列
Address address=new Address();
address.addPerson(持久化的Person);
session.save(address)
没有问题涩。
下面继续。有链接表的
可以两端都有JoinTable unique熟悉必须的涩,因为是1-1必须具有唯一性另外一段不想控制关系,可以使用mappedBy涩.简单搞定
@Entity
@Table(name="person_inf")
public class Person
{
@Id @Column(name="person_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String name;
private int age;
// 定义该Person实体关联的Address实体
@OneToOne(targetEntity=Address.class)
// 映射底层连接表,表名为person_address
@JoinTable(name="person_address",
// 映射连接表的外键列,增加unique=true表明是1-1关联
joinColumns=@JoinColumn(name="person_id"
, referencedColumnName="person_id" , unique=true),
// 映射连接表的外键列,增加unique=true表明是1-1关联
inverseJoinColumns=@JoinColumn(name="address_id"
, referencedColumnName="address_id", unique=true)
)
private Address address;
… 不想贴了