读书人

Hibernate投射关系全面解析

发布时间: 2012-09-11 10:49:03 作者: rapoo

Hibernate映射关系全面解析
Hibernate映射关系全面解析,到今天为止,对Hibernate映射的郁闷已经无法再抑制了,也必须该和这个烦人的家伙做个了断了,下面将对Hibernate各种映射做一一细解。


Hibernate映射关系到底有多复杂?

传说中的不就是分为3种嘛,一对多,一对一,多对多,如果你真正用过hibernate你会发现实际的开发过程中遇到的情况要比概念的3种情况更为复杂。

对映关系还会分为单项关联和双项关联,而单项和双项关系,外键在主表还是从表又分两种情况,级联保存2个表的时候,有一种情况会多一条update语句,而有一种情况则只需要2条insert语句,究竟原理和王者解决之道,今天将水落石出了。


下面开始正题,首先目标瞄准,一对多关联关系。

建表:
学生表:create table student (id int primary key auto_increment,name varchar(30));

地址表:create table address (id int primary key auto_increment,city varchar(30),student_id int);

一个学生可以有多个地址,形成一对多的关系,而关联关系的外键由地址表维护!!


首先介绍单向一对多关联关系,外键在从表中

单向学生表PO

package cn.limaoyuan.hibernate.po.single;import java.util.HashSet;import java.util.Set;import javax.persistence.CascadeType;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.OneToMany;import javax.persistence.Table;@Entity@Table(name="student")public class Student {private Integer id;private String name;private Set<Address> addressSet = new HashSet<Address>(0);@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="name")public String getName() {return name;}public void setName(String name) {this.name = name;}/** * 单向一对多,外键在地址表中维护 * */@OneToMany(cascade=CascadeType.ALL) //CascadeType.All设成级联保存,以便演示更多效果。//这里没有mappedBy映射而是用@JoinColumn来代替,因为我们用的是单向关联关系,在Address中不存在student属性,只有studentId属性,所以我们用@JoinColumn来映射2张表之间的关系关系。@JoinColumn(name="student_id")public Set<Address> getAddressSet() {return addressSet;}public void setAddressSet(Set<Address> addressSet) {this.addressSet = addressSet;}}


单向地址表PO
package cn.limaoyuan.hibernate.po.single;import java.io.Serializable;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name="address")public class Address implements Serializable{private Integer id;private String city;private Integer studentId;@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="city")public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Column(name="student_id")public Integer getStudentId() {return studentId;}public void setStudentId(Integer studentId) {this.studentId = studentId;}}

因为是单向一对多关联,所以地址表PO没有更多的注解,只是单纯的映射与数据库之间的基础对应。

学生Dao接口
package cn.limaoyuan.hibernate.dao;import cn.limaoyuan.hibernate.po.single.Student;public interface IStudentDao {public void insert(Student student);}


学生Dao实现
package cn.limaoyuan.hibernate.dao;import org.hibernate.Session;import cn.limaoyuan.hibernate.po.single.Student;import cn.limaoyuan.hibernate.util.HibernateSessionFactory;public class StudentDaoImpl implements IStudentDao{@Overridepublic void insert(Student student) {Session session = HibernateSessionFactory.getSession();session.beginTransaction();session.save(student);session.getTransaction().commit();session.close();}}


测试保存一个学生类:
/** * 测试插入单向一对多关联关系  * */public static void insertStudent(){IStudentDao studentDao = new StudentDaoImpl();Student student = new Student();student.setName("zhang san");Address address = new Address();address.setCity("beijing");address.setStudentId(student.getId());student.getAddressSet().add(address);                   //这里直接插入学生,会级联插入地址studentDao.insert(student);}


测试结果没有问题,学生表和地址表分别插入了一条数据,但是我们观察SQL输出了3条语句!
2010-02-23 14:39:54,906 DEBUG [main] SQL.log(401) | insert into student (name) values (?)2010-02-23 14:39:54,968 DEBUG [main] SQL.log(401) | insert into address (city, student_id) values (?, ?)2010-02-23 14:39:55,015 DEBUG [main] SQL.log(401) | update address set student_id=? where id=?


我们插入了2条数据,为什么后面多了一条update语句呢?

原因在于,我们这个单项的关联关系在student类表示,系统当保存address类的时候,他不知道studentId这个值是否已经保存过了,所以在2条insert之后会保险的在最后update一下studentId字段,如此说来,我们要让address类知道有student类存在的话,那么address类是不是就不会在最后有这一条update语句了呢?答案是肯定的,这就是我下面要说的双向关联关系。

由于这是技术笔记,双向关联关系的好处就不说太多了,明显的是级联保存时会节省一条UPDATE语句,第2在双方的类中都很容易找到上一级关联对象。

介绍双向一对多关联关系,外键在从表中

双向学生表PO
package cn.limaoyuan.hibernate.po.both;import java.util.HashSet;import java.util.Set;import javax.persistence.CascadeType;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.OneToMany;import javax.persistence.Table;@Entity@Table(name="student")public class StudentBoth {private Integer id;private String name;private Set<AddressBoth> addressSet = new HashSet<AddressBoth>(0);@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="name")public String getName() {return name;}public void setName(String name) {this.name = name;}/** * 单向一对多,外键在地址表中 * */         //由于用了双项关联,那么在Address中就会有了student属性,那么我们就可以简单的使用mappedBy来指向address类中的student属性来关联这组关系。@OneToMany(cascade=CascadeType.ALL,mappedBy="student")public Set<AddressBoth> getAddressSet() {return addressSet;}public void setAddressSet(Set<AddressBoth> addressSet) {this.addressSet = addressSet;}}


双向地址表PO
package cn.limaoyuan.hibernate.po.both;import java.io.Serializable;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.ManyToOne;import javax.persistence.Table;import javax.persistence.Transient;@Entity@Table(name="address")public class AddressBoth implements Serializable{private Integer id;private String city;private Integer studentId;private StudentBoth student;@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="city")public String getCity() {return city;}public void setCity(String city) {this.city = city;}@Transientpublic Integer getStudentId() {return studentId;}public void setStudentId(Integer studentId) {this.studentId = studentId;}         /*这里由于使用了双向关联关系,那么就直接把数据库中的student_id字段直接映射成student属性,而不用单独映射成一个studentId属性,然后用@JoinColumn()注解的name属性去指定以本类中的哪个属性去关联对方,默认的是去关联对方的主键值,我们也可以用referencedColumnName属性去指定关联主表中的某个值来确认关系。@JoinColumn(name="student_id")的含义就是用address中的student_id字段去关联student表中的id字段,这里我们在@JoinColumn中省略了referencedColumnName="id",加上了也会得到同样的效果。*/@ManyToOne@JoinColumn(name="student_id")public StudentBoth getStudent() {return student;}public void setStudent(StudentBoth student) {this.student = student;}}


下面来测试一下双向关联的情况
//双向关联保存,外建在对方public static void insertStudentBoth(){IStudentDao studentDao = new StudentDaoImpl();StudentBoth student = new StudentBoth();student.setName("zhang san");AddressBoth address = new AddressBoth();address.setCity("beijing");//address.setStudentId(student.getId());address.setStudent(student); //地址表关联学生student.getAddressSet().add(address); //学生添加地址studentDao.insert(student); //保存学生}


执行后同样会在学生表和地址表各插入一表数据,然而我们只会得到2条insert语句。
2010-02-23 14:57:28,000 DEBUG [main] SQL.log(401) | insert into student (name) values (?)2010-02-23 14:57:28,015 DEBUG [main] SQL.log(401) | insert into address (city, student_id) values (?, ?)

这显然是我们更想要的结果。

介绍单向一对一关联关系,外键在从表中

还是刚才的二张表,我们更改业务逻辑,让一个学生只能有一个地址对应。

哈哈,你上当了,OneToOne的方式,如果关联外建在从表中,那么将不能映射。
OneToOne的方式,要么关键外键在主表中,要么就设成双向关联关系。


介绍双向一对一关联关系,外键在从表中

双向学生表PO
package cn.limaoyuan.hibernate.po.single2;import java.util.HashSet;import java.util.Set;import javax.persistence.CascadeType;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.ManyToOne;import javax.persistence.OneToMany;import javax.persistence.OneToOne;import javax.persistence.Table;@Entity@Table(name="student")public class StudentSingle2 {private Integer id;private String name;private AddressSingle2 address;@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="name")public String getName() {return name;}public void setName(String name) {this.name = name;}/** * 双向一对一,外键在对方,一对一可以用OneToOne来映射,由于外键在对方,那么OneToOne方式只能用mappedBy去引用。 **/@OneToOne(mappedBy="student",cascade=CascadeType.ALL)public AddressSingle2 getAddress() {return address;}public void setAddress(AddressSingle2 address) {this.address = address;}}


双向地址表PO
package cn.limaoyuan.hibernate.po.single2;import java.io.Serializable;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.OneToOne;import javax.persistence.Table;@Entity@Table(name="address")public class AddressSingle2 implements Serializable{private Integer id;private String city;private StudentSingle2 student;@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="city")public String getCity() {return city;}public void setCity(String city) {this.city = city;}@OneToOne@JoinColumn(name="student_id") //自己的student_id字段去关联studnet表中的id属性,如果不是关联student主键,那么可以用referencedColumnName指定。public StudentSingle2 getStudent() {return student;}public void setStudent(StudentSingle2 student) {this.student = student;}}


测试双向一对一外键在从表中的情况 :
/** * 测试插入单向一对多关联关系 ,外建在本方 * */public static void insertStudentSingle2(){IStudentDao studentDao = new StudentDaoImpl();StudentSingle2 student = new StudentSingle2();student.setName("zhang san");AddressSingle2 address = new AddressSingle2();address.setCity("beijing");address.setStudent(student); //地址绑定学生student.setAddress(address); //学生绑定地址studentDao.insert(student); //插入学生}


结果在学生和地址表中各插了一条,打印出2条SQL
2010-02-23 15:24:21,546 DEBUG [main] SQL.log(401) | insert into student (name) values (?)2010-02-23 15:24:21,562 DEBUG [main] SQL.log(401) | insert into address (city, student_id) values (?, ?)


并没有打印出多余的UPDATE语句,可见只要是双向关联后无论是多对一还是一对多,都可以很好的运行级联插入操作,并不会去执行多余的SQL语句。

介绍单向一对一关联关系,外键在主表中

首先建表,还是学生表和地址表。
create table student2 (id int primary key auto_increment,name varchar(30),address_id int);

create table address2 (id int primary key auto_increment,city varchar(20));

和之前演示的2张表,唯一的不同就是关联关系交给了主表,也就是学生表,在学生表中多了address_id字段,做为外键去引用address中的id字段,形成关联关系。

单向一对一学生表
package cn.limaoyuan.hibernate.po.single3;/** * 一对一单项关联,外键在本方 * */import javax.persistence.*;@Entity@Table(name="student2")public class StudentSingle3 {private Integer id;private String name;private AddressSingle3 address;@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="name")public String getName() {return name;}public void setName(String name) {this.name = name;}/** * 单向一对多,外键在本方,一对一可以用OneToOne来映射 * */@OneToOne(cascade=CascadeType.ALL)@JoinColumn(name="address_id")public AddressSingle3 getAddress() {return address;}public void setAddress(AddressSingle3 address) {this.address = address;}}


单向一对一地址表PO:
package cn.limaoyuan.hibernate.po.single3;import java.io.Serializable;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name="address2")public class AddressSingle3 implements Serializable{private Integer id;private String city;@Id@GeneratedValue(strategy=GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Column(name="city")public String getCity() {return city;}public void setCity(String city) {this.city = city;}}


测试类
/** * 测试插入单向一对一关联关系 ,外建在本方 * */public static void insertStudentSingle3(){IStudentDao studentDao = new StudentDaoImpl();StudentSingle3 student = new StudentSingle3();student.setName("zhang san");AddressSingle3 address = new AddressSingle3();address.setCity("beijing");student.setAddress(address);studentDao.insert(student);}


结果很正确,在学生和地址表各插入了一条,并且只打印了2条SQL,这是我们第一次在单项关联的情况下插入2张表只用了2条数据,可见非常好。
2010-02-23 15:53:29,453 DEBUG [main] SQL.log(401) | insert into address2 (city) values (?)2010-02-23 15:53:29,468 DEBUG [main] SQL.log(401) | insert into student2 (address_id, name) values (?, ?)


介绍双向一对一关联关系,外键在主表中
相信结果和双向一对一,外键在从表中的结果是一样的,就不再做测试了,附件是完整的例子。


介绍利用中间表来关联单向一对一关联关系,外键在关联表中

建表:
学生表:create table student3 (id int primary key auto_increment,name varchar(30));
地址表:create table address3 (id int primary key auto_increment,city varchar(30));
关联表:create table student3_address3 (student_id int,address_id int);




由于上传大小限制,完整的例子在163邮箱的网盘我的文档目录中,文件名字为hibernatetest.rar

读书人网 >软件架构设计

热点推荐