在上一篇文章里。我们从端方向一端建立关联关系,完毕了从文章到作者的关联关系建立。但在实际的博客站点中,用户肯定还须要获取自己所写的文章,这时能够建立用户(一)对文章(多)的单向关联映射。

先来看我们的一方配置实例

package com.zeng.model;

import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table; @Entity//声明当前类为hibernate映射到数据库中的实体类
@Table(name = "t_user")//声明在数据库中自己主动生成的表名为t_user
public class User {
@Id//声明此列为主键,作为映射对象的标识符
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String name;
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,mappedBy = "user",targetEntity = Article.class,orphanRemoval = true)//用户作为一方使用OneToMany注解
private Set<Article> articles;//文章作为多方。我们使用Set集合来存储。同一时候还能防止存放同样的文章
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Set<Article> getArticles() {
return articles;
}
public void setArticles(Set<Article> articles) {
this.articles = articles;
}
//重写hashcode方法提高比較效率
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
//重写equals比較对象相等
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

以下是我们相应的多方配置

package com.zeng.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table; @Table(name = "t_article1")
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String content; public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}

依据这些配置。我们来编写測试方法:

package com.zeng.test;

import java.util.HashSet;
import java.util.Set; import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.zeng.model.Article;
import com.zeng.model.User; public class Test2 {
private ApplicationContext ac;
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
@BeforeClass//在測试类初始化时调用此方法。完毕静态对象的初始化
public static void before(){
}
@Before//每个被注解Test方法在调用前都会调用此方法一次
public void setup(){//建立针对我们当前測试方法的的会话和事务
ac = new ClassPathXmlApplicationContext("spring-datasource.xml");
sessionFactory = (SessionFactory) ac.getBean("sessionFactory");
session = sessionFactory.openSession();
transaction = session.beginTransaction();
}
//測试一对多单向关联
@Test
public void test2(){
User user = new User();
user.setName("oneObject");
Set<Article> articles = new HashSet<Article>();
for(int i = 0 ; i < 3;i ++){//加入三篇文章
Article article = new Article();
article.setContent("moreContent" + i) ;
articles.add(article);
}
user.setArticles(articles);//建立关联关系
session.save(user);//仅保存用户
} @After//每个被注解Test方法在调用后都会调用此方法一次
public void teardown(){
if(transaction.isActive()){//假设当前事务尚未提交,则
transaction.commit();//提交事务,主要为了防止在測试中已提交事务,这里又反复提交
} session.clear();
session.close();
sessionFactory.close();
}
@After//在类销毁时调用一次
public void after(){
} }

执行測试方法。我们会看到控制台打印下列sql语句:



Hibernate: insert into t_user1 (name) values (?)

Hibernate: insert into t_article1 (content) values (?)

Hibernate: insert into t_article1 (content) values (?)

Hibernate: insert into t_article1 (content) values (?)

Hibernate: insert into t_user1_t_article1 (t_user1_id, articles_id) values (?, ?)

Hibernate: insert into t_user1_t_article1 (t_user1_id, articles_id) values (?, ?)

Hibernate: insert into t_user1_t_article1 (t_user1_id, articles_id) values (?

, ?)

在前四句,我们看到在保存user对象时。级联保存了我们的文章对象,最后面三条信息又是什么?原来在我们没有设置@JoinColumn(详细用法请參考我的上篇文章)。

那么在一对多的关联配置中。hibernate会默认帮我们生成中间表来完毕两者的映射关系,查询数据库。我们会发现

mysql> select * from t_user1_t_article1;

+————+————-+

| t_user1_id | articles_id |

+————+————-+

| 1 | 1 |

| 1 | 2 |

| 1 | 3 |

+————+————-+

3 rows in set (0.00 sec)



确实是通过中间表,将用户和文章关联起来了。

这时进行级联删除測试:

“`java

User user = (User) session.get(User.class, 1);

session.delete(user);

>我们会得到信息打印:
>
Hibernate: delete from t_user1_t_article1 where t_user1_id=?
Hibernate: delete from t_article1 where id=? Hibernate: delete from t_article1 where id=?
Hibernate: delete from t_article1 where id=?
Hibernate: delete from t_user1 where id=?
</font>
可见,它的删除顺序是<font color=red>先清楚中间表数据->再删除多方文章4数据->最后清楚一方用户数据</font> 假设我们不像使用中间表,而想像上一篇配置多对一关联那样,在文章表生成user_id,我们就要一方配置@JoinColumn属性,相应属性的实比例如以下:
```java
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,targetEntity = Article.class,orphanRemoval = true)//用户作为一方使用OneToMany注解
@JoinColumn(name = "user_id")//加入了这个注解
private Set<Article> articles;//文章作为多方,我们使用Set集合来存储,同一时候还能防止存放同样的文章 <div class="se-preview-section-delimiter"></div>

改动相应表名,让hibernate又一次在数据库中生成表,此时再执行我们的測试方法,会看到:



Hibernate: insert into t_user2 (name) values (?)

Hibernate: insert into t_article2 (content) values (?)

Hibernate: insert into t_article2 (content) values (?)

Hibernate: insert into t_article2 (content) values

Hibernate: update t_article2 set user_id=? where id=?

Hibernate: update t_article2 set user_id=? where id=?

Hibernate: update t_article2 set user_id=? where id=?



此时我们会看到,最后三行换成了更新我们的表属性值。

从这里我们看出为了更新aritlce表中user_id相应值,我们额外使用多了三条数据,这是非常不值的,会额外消耗数据库的性能,有没方法使得在插入文章表的同一时候插入user_id的值呢?查看hibernate源代码,我们会发现这是由于user作为主动方,它处理关联对象时必须通过update来完毕,假设我们想取消update,应该将user放弃主动。让还有一方(多方)去维护,这又涉及到我们的一对多、多对一双向关联了,我们在下一篇文章再详细解决这一问题。

这个时候我们来測试级联删除:

User user = (User) session.get(User.class, 1);
session.delete(user); <div class="se-preview-section-delimiter"></div>

会得到例如以下信息打印:

Hibernate: update t_article2 set user_id=null where user_id=?

Hibernate: delete from t_article2 where id=?

Hibernate: delete from t_article2 where id=?

Hibernate: delete from t_article2 where id=?

Hibernate: delete from t_user2 where id=?

注意到,它的删除顺序是:清除用户表和文章表的关联关系(这又是由于用户表作为主动方,它必须通过此方法来维护关联关系->然后清除多方文章信息->最后才删除我们的一方用户

上面我们基本完毕了我们的測试工作。以下我们对配置属性加以分析:

1. 相对于上一篇我们提到的ManyToOne属性。OneToMany独有的属性有:mapperBy和orphanRemoval,mpperBy是指放弃维护级联关系,详细我们在双向关联中再详细分析,这里比較独特的属性是orphanRemoval

表面意思是去除孤儿,当一方不再关联多方某一实体A时,自己主动从数据库中删除A。以下来看实例測试。假如我们先将其设为false

@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,targetEntity = Article.class,orphanRemoval = false)//用户作为一方使用OneToMany注解
@JoinColumn(name = "user_id")
private Set<Article> articles;//文章作为多方。我们使用Set集合来存储,同一时候还能防止存放同样的文章 <div class="se-preview-section-delimiter"></div>

先看看我们数据库的初始记录信息:

+—-+———–+

| id | name |

+—-+———–+

| 2 | oneObject |

+—-+———–+

1 row in set (0.00 sec)

mysql> select * from t_article2;

+—-+————–+———+

| id | content | user_id |

+—-+————–+———+

| 6 | moreContent0 | 2 |

| 5 | moreContent1 | 2 |

| 4 | moreContent2 | 2 |

+—-+————–+———+

3 rows in set (0.00 sec)

接着開始我们的測试:

User user = (User) session.get(User.class,2);
Article article = user.getArticles().iterator().next();//获取与用户有相应关系的一篇文章
user.getArticles().remove(article);//从用户相应关系中清除出来
session.update(user);//更新用户 <div class="se-preview-section-delimiter"></div>

执行測试代码,我们会看到控制台仅输出一条sql语句:

Hibernate: update t_article2 set user_id=null where user_id=? and id=?



查询数据库。此时变成:

mysql> select * from t_article2;

+—-+————–+———+

| id | content | user_id |

+—-+————–+———+

| 6 | moreContent0 | 2 |

| 5 | moreContent1 | 2 |

| 4 | moreContent2 | NULL |

+—-+————–+———+

3 rows in set (0.00 sec)

如今我们先恢复測试前的数据:

mysql> update t_article2 set user_id = 2 where id = 4;

然后再将相应注解里的orphanRemoval设为true

@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,targetEntity = Article.class,orphanRemoval = true)
@JoinColumn(name = "user_id")
private Set<Article> articles; <div class="se-preview-section-delimiter"></div>

再次执行我们的測试代码,控制台输出:

Hibernate: update t_article2 set user_id=null where user_id=? and id=?

Hibernate: delete from t_article2 where id=?

我们的文章记录就相应被删除了,这就是去除“孤儿”的含义。在实际开发中。正如我们的身份证总是从属于某个人的,假设失去这样的从属关系,身份证就没有意义而能够去除了。

2. @JoinTable

在默认不使用@JoinColumn时,多对一关联中hibernate会为我们自己主动生成中间表。但假设我们像自己来配置中间表,就能够使用@JoinTable注解。它的实例配置例如以下:

@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,targetEntity = Article.class,orphanRemoval = true)//用户作为一方使用OneToMany注解
@JoinTable(name = "t_user_articles",inverseJoinColumns = {@JoinColumn(name = "article_id")},joinColumns = {@JoinColumn(name = "article_id")})
private Set<Article> articles;//文章作为多方。我们使用Set集合来存储,同一时候还能防止存放同样的文章 <div class="se-preview-section-delimiter"></div>

当中:

1. name为创建的中间表名称。

2. inverseJoinColumns指向对方的表,在这里指多方的表。

3. joinColumns指向自己的表,即一方的表。这些指向都是通过主键映射来完毕的。

执行我们的測试代码:

User user = new User();
user.setName("oneObject");
Set<Article> articles = new HashSet<Article>();
for(int i = 0 ; i < 3;i ++){
Article article = new Article();
article.setContent("moreContent" + i) ;
articles.add(article);
}
user.setArticles(articles);//建立关联关系
session.save(user); <div class="se-preview-section-delimiter"></div>

会发现我们的用户、文章相应关系都在中间表中建立起来了:

mysql> select * from t_user_articles;

+———+————+

| user_id | article_id |

+———+————+

| 1 | 1 |

| 1 | 2 |

| 1 | 3 |

+———+————+

3 rows in set (0.00 sec)

使用中间表的优点是对原来两张表的结构不正确造成不论什么影响。

尤其是在一些老项目中我们能够不改动既定的表结构(其实在一个项目古老庞大到一定程度就非常难去改)、以不侵入原来表的方式构建出一种更清淅更易管理的关系

当然缺点是我们的我们须要维护多一张表。一旦中间表多了,维护起来会愈加麻烦。但综合来看。我们显然更推荐用中间表的方式来完毕配置

3. eqauls和hashCode方法

在我们開始配置一对多的一方时,我们通过Set来和多方建立关系,当中提到的一点是能够防止多方同样对象出现。

这个同样相应我们数据库中就是某些属性列同样。比方:对于Article,假设id和content在两条记录中都一样,我们就能够觉得两条记录是一致的,因此会自己主动去重那么我们来推断它们的反复关系呢?这个时候就要通过重写hashCode和equals方法了。

示比例如以下:

//重写equals比較对象相等
@Override
public boolean equals(Object obj) {
if (this == obj)//假设地址引用同样,直接推断为相等
return true;
if (obj == null)//假设目标对象为null,直接推断不等
return false;
if (getClass() != obj.getClass())//两者类不一致
return false;
User other = (User) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))//推断两者id是否都存在且相等
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))//推断两者名字是否都存在且相等
return false;
return true;
}

一般来说,我们重写equal方法就能推断两个对象是否相等了,为什么还要重写hashCode方法呢?主要是考虑到效率的问题,对于equals方法,当比較规则比較复杂的话就会比較耗时了,而hashCode为每个对象生成一个散列码(通过一种神奇的算法。一般为关键属性乘以一个质数),避免了比較慢的运算。只是我们不能由于快就单凭hash码来推断两个对象是否相等。由于hashCode并不能保证能为每个不同的对象生成唯一的散列码,所以可能会有两个hash码同样,但对象确实不一致的情况。

只是我们知道的是假设连hash码都不一致,那两个对象肯定是不一致的

依据此思路。我们能够非常好地理解在java内部,是怎样推断两个对象是否相等的:

hibernate5(10)注解映射[2]一对多单向关联的更多相关文章

  1. hibernate5(9)注解映射[1]多对一单向关联

    在博客站点中,我们可能须要从某一篇文章找到其所关联的作者.这就须要从文章方建立起对用户的关联,即是多对一的映射关系. 如今先看一个配置实例:我们的文章实体类 package com.zeng.mode ...

  2. hibernate5(12)注解映射[4]一对一外键关联

    在实际博客站点中,文章内容的数据量非常多,它会影响我们检索文章其他数据的时间,如查询公布时间.标题.类别的等. 这个时候,我们能够尝试将文章内容存在还有一张表中,然后建立起文章--文章内容的一对一映射 ...

  3. Hibernate从入门到精通(八)一对多单向关联映射

    上次的博文Hibernate从入门到精通(七)多对一单向关联映射我们主要讲解了一下多对一单向关联映射,这次我们继续讲解一下一对多单向映射. 一对多单向关联映射 在讲解一对多单向关联之前,按照我们的惯例 ...

  4. Hibernate(八)一对多单向关联映射

    上次的博文Hibernate从入门到精通(七)多对一单向关联映射我们主要讲解了一下多对一单向关联映射, 这次我们继续讲解一下一对多单向映射. 一对多单向关联映射 在讲解一对多单向关联之前,按 照我们的 ...

  5. Hibernate一对多单向关联和双向关联映射方法及其优缺点 (待续)

    一对多关联映射和多对一关联映射实现的基本原理都是一样的,既是在多的一端加入一个外键指向一的一端外键,而主要的区别就是维护端不同.它们的区别在于维护的关系不同: 一对多关联映射是指在加载一的一端数据的同 ...

  6. hibernate之关于一对多单向关联映射

    [hibernate]之关于一对多单向关联映射 基于外键的一对多关联映射! 一对多,Group(组)对于Person(人),一个组能够有多个人!ok? Hibernate主要有两种配置方法.一种是An ...

  7. Hibernate 一对多单向关联Demo

    以Classes[班级]和Student[学生]为例的Demo Classes .java public class Classes implements Serializable { private ...

  8. Java进阶知识09 Hibernate一对多单向关联(Annotation+XML实现)

    1.Annotation 注解版 1.1.在一的一方加Set 1.2.创建Customer类和Order类 package com.shore.model; import java.util.Hash ...

  9. Hibernate,关系映射的多对一单向关联、多对一双向关联、一对一主键关联、一对一外键关联、多对多关系关联

    2018-11-10  22:27:02开始写 下图内容ORM.Hibernate介绍.hibername.cfg.xml结构: 下图内容hibernate映射文件结构介绍 下图内容hibernate ...

随机推荐

  1. linux shell脚本监控进程是否存在

    用shell脚本监控进程是否存在 不存在则启动的实例,先上代码干货:    #!/bin/shps -fe|grep processString |grep -v grepif [ $? -ne 0 ...

  2. 开发者选择短视频SDK,为何青睐七牛云?

    从文字到图片再到视频的互联网内容媒介发展途径,随着 5g 技术的逐渐落地愈发清晰.短视频市场中的角力也随着诸多资本和创业者的涌入,进入到白热化阶段.这样的情况下,选择合适的短视频SDK产品就显得尤为重 ...

  3. 【Luogu】P1486郁闷的出纳员(Splay)

    题目链接 名副其实的调了一下午…… 每做一道题都是对我那不规范的Splay代码的刀刻斧凿一般的修正啊…… Splay.如果有一批员工不干了,那就找还能干的薪水最少的员工,把它splay到根,删除它的左 ...

  4. Python基础教程总结(二)

    上周总结了一下Python的一些基本数据类型和用法.这次总结一下4-9章的内容,完后,赶紧学以致用吧. 5. 第四章——字典:当索引不好用时 字典是Python中唯一内建的映射类型.字典中的值并没有特 ...

  5. 标准C程序设计七---53

    Linux应用             编程深入            语言编程 标准C程序设计七---经典C11程序设计    以下内容为阅读:    <标准C程序设计>(第7版) 作者 ...

  6. php set_time_limit(0) 设置程序执行时间的函数

    一个简单的例子,在网页里显示1500条语句,如果未设置失效时间,则程序执行到791时结束了,如果把 set_time_limit(0); 前的注释符//去除,则程序直到1才结束.   set_time ...

  7. java通过代码控制线程状态,解决线程不安全的问题。

    写两个类,Input,output 两个都是使用同步代码块的方式实现线程间的同步 input类,是为变量赋值 output类,是打印变量 由于线程争夺cpu造成数据的不匹配 通过,设立一个 flag ...

  8. LeetCode OJ--Remove Duplicates from Sorted Array

    http://oj.leetcode.com/problems/remove-duplicates-from-sorted-array/ 删除数组中的重复元素,要求为原地算法. 进行一遍遍历,记录下一 ...

  9. 无法安装64位版本的visio/office,因为在您的PC上找到了以下32位程序的解决办法

    解决方案:按下win+R键,打开运行,输入regedit,打开注册表,依次定位到 HKEY_CLASSES_ROOT\Installer\Products,展开Products后  将这些“00005 ...

  10. 使用sqlparse分析SQL语句,及自己写的SQL分析语句

    备忘, 以后写的时候可以参考. #!/usr/bin/env python # -*- coding: utf-8 -*- import sqlparse import re sql = " ...