我们都知道,在数据库中存在了多种表与表的映射关系,如一对一、一对多、多对一、多对多等各种关系,那么在hibernate中这些表与表之间的关系如何表示呢?下面我们来详细研究下吧。
首先我们来看看多对一的关联关系吧。
many-to-one,多对一这种关联关系在现实社会和程序中都是最常见的关联关系了,如学生和班级的关系。这种关系我们一般是通过多方来关联一方,在多方建立外键,如在学生中创建班级的外键完成关联。
Student.java:
package com.lzcc.hibernate.entity;
publicclass Student {
privateintid; private Stringname; private StringstuNo; //我们在这个使用classID也行,但是不建议使用,可以使用对象 // privateint classId; private ClassRoomclassRoom;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public String getStuNo() { returnstuNo; }
publicvoid setStuNo(String stuNo) { this.stuNo = stuNo; }
public ClassRoom getClassRoom() { returnclassRoom; }
publicvoid setClassRoom(ClassRoom classRoom) { this.classRoom = classRoom; }
public Student() { }
public Student(int id, String name, String stuNo, ClassRoom classRoom) { super(); this.id = id; this.name = name; this.stuNo = stuNo; this.classRoom = classRoom; }
@Override public String toString() { return"Student [id=" +id + ", name=" +name +", stuNo=" + stuNo + ", classRoom=" + classRoom +"]"; } } |
Student.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Student"table="t_stu"> <idname="id"> <generator class="native"/> id> <propertyname="name"/> <propertyname="stuNo"/>
<many-to-onename="classRoom"column="cId"/> class> hibernate-mapping> |
ClassRoom.java:
package com.lzcc.hibernate.entity;
publicclass ClassRoom {
privateintid; private Stringname;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public ClassRoom() { }
public ClassRoom(int id, String name) { super(); this.id = id; this.name = name; }
@Override public String toString() { return"ClassRoom [id=" +id + ", name=" +name +"]"; } } |
ClassRoom.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="ClassRoom"table="t_cls_room"> <idname="id"> <generator class="native"/> id> <propertyname="name"/> class> hibernate-mapping> |
再讲两个实体类的配置文件添加到hibernate.cfg.xml文件中:
<mappingresource="com/lzcc/hibernate/entity/ClassRoom.hbm.xml"/> <mapping resource="com/lzcc/hibernate/entity/Student.hbm.xml"/> |
可以看出,我们的整体设计就是在多方维护关系,一方不做任何关联。
下面我们通过单元测试来看看many-to-one
@Test publicvoid testAdd01() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); //我们先添加一方,再添加多方 ClassRoom c = new ClassRoom(); c.setName("梁山班"); session.save(c); Student stu1 = new Student("宋江","001", c); session.save(stu1); Student stu2 = new Student("吴用","003", c); session.save(stu2);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); }
} |
我们运行代码发现发送了三条sql语句,符合我们的理解,三个对象,保存时需要三条sql语句来完成。
下面我们如果先添加多的一方呢?
@Test publicvoid testAdd02() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); //如果我们先添加多的一方呢? Student stu1 = new Student(); stu1.setName("孙悟空"); stu1.setStuNo("001"); session.save(stu1); Student stu2 = new Student(); stu2.setName("猪八戒"); stu2.setStuNo("002"); session.save(stu2); ClassRoom c = new ClassRoom(); c.setName("西游班"); session.save(c); stu1.setClassRoom(c); stu2.setClassRoom(c); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
代码运行效果如下:
我们发现发送了五条sql语句,这样就比上面的多发送了两条sql,因为我们先插入多的一方,而此时,多的一方不知道外键(也就是一方的id),无法完成关联,需要等待一方完成插入,多方在更新数据,让外键插入。所以在many-to-one中,我们要先插入一方,再插入多方,记住。
下面我们来看一个hibernate的级联方式,在hibernate的实体类映射文件中,many-to-one节点,有个属性:cascade
@Test publicvoid testAdd03() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); //我们先添加一方,再添加多方 ClassRoom c = new ClassRoom(); c.setName("红楼班");
Student stu1 = new Student("贾宝玉","001", c); session.save(stu1); Student stu2 = new Student("林黛玉","003", c); session.save(stu2);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); }
} |
运行这段代码报错了:
报错我们都能理解,因为我们的c对象没有保存,意味着外键没有保存,那么Student引用的外键不存在,肯定会报错的,但是如果我们在many-to-one上加入cascade=“all”,注意all并不是唯一值,它的值可以是update、delete……等等,表示在哪种操作,all表示所有操作,一旦我们加入了all,那么再运行这段代码我们发现不报错了,而且外键也被hibernate自动关联了:
<many-to-onename="classRoom"column="cId"cascade="all"/> |
但是注意同学们,我们很少使用cascade,特别是在多方,更是很少使用。注意:cascade我们一般只在特殊情况下使用,如删除一方时,存在多方,这时我们可以关联删除,但是一般我们也不会这样使用,我们一般在删除一方的时候,要求先删除多方,再来删除一方,如果你有特殊要求,才会使用cascade。
下面我们来看load方法。
@Test publicvoid testLoad01() { Session session = null; try { session = HibernateUtil.openSession(); Student stu = (Student) session.load(Student.class, 1); System.out.println(stu.getName()); System.out.println(stu.getClassRoom().getName()); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
运行代码,我们发现hibernate共发送了两条sql来完成了查询,注意:在hibernate的many-to-one的关联关系中,load也存在延迟加载问题,当我们没有使用外键关联的对象时,hibernate不会去取,当我们需要时,才会发送sql去查询,解决这个问题,我们后面会讲到的,使用hibernate的抓取策略。
hibernate的one-to-many单向,在实际开发中我们一般不会使用,为什么呢?下面我们来详细看看,大家就明白了。
我们就以帖子的发言(Message)和对发言的回复(Comment)为例
Message.java:
package com.lzcc.hibernate.entity;
import java.util.Set;
publicclass Message {
privateintid; private Stringtitle; private Stringcontent; //我们可以使用list,但是一般我们使用set,其实那个都行 // private List private Set
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getTitle() { returntitle; }
publicvoid setTitle(String title) { this.title = title; }
public String getContent() { returncontent; }
publicvoid setContent(String content) { this.content = content; }
public Set returncomments; }
publicvoid setComments(Set this.comments = comments; }
public Message(String title, String content, Set super(); this.title = title; this.content = content; this.comments = comments; }
public Message() { } } |
Comment.java:
package com.lzcc.hibernate.entity;
publicclass Comment {
privateintid; private Stringcontent;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getContent() { returncontent; }
publicvoid setContent(String content) { this.content = content; }
public Comment() { }
public Comment(String content) { this.content = content; } } |
Message.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Message"table="t_message"> <idname="id"> <generator class="native"/> id> <propertyname="title"/> <propertyname="content"/>
<setname="comments"> <key>
<column name="mid">column> key> <one-to-many class="Comment"/> set>
class> hibernate-mapping> |
Comment.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Comment"table="t_comment"> <idname="id"> <generator class="native"/> id> <propertyname="content"/> class> hibernate-mapping> |
注意,one-to-many的配置主要是在一方的属性中增加一个集合,来储存多方的数据,配置文件的写法根据这个集合来确定,key节点表示外键(外键还是使用多方维护),只是代码中在一方中保存多方。
配置完成,下面我们来测试看看,为什么我们一般不使用一方来维护多方呢?
我们发现本身应该4条sql就完成添加了,但是发送了7条sql,因为我们代码在一方维护,数据库中使用的是多方关联维护一方,这样就导致在完成4条数据添加后,需要再发送3条更新语句,完成对多方的外键的更新。这就是我们为什么不建议使用one-to-many的单向关联的原因。
下面我们来测试load方法。
@Test publicvoid testLoad() { Session session = null; try { session = HibernateUtil.openSession(); Message m = (Message) session.load(Message.class, 1); System.out.println(m.getId()); System.out.println(m.getContent()); for (Comment c : m.getComments()) { System.out.println(c.getContent()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); }
} |
我们发现还是存在延迟加载问题,需要2条sql语句来完成查询。当然在查询集合时,是一条sql查询,这样效率也可以的(至少没有一个对象1条sql),但是这些其实我们是可以使用一条sql查询出来的,这个就是抓取策略,后面我们会讲到的。
所以注意(最佳实践):hibernate中one-to-many的单向关联我们一般不会使用的。
下面我们来测试下,如果我们要查询一方集合的数量呢?
@Test publicvoid testCount() { Session session = null; try { session = HibernateUtil.openSession(); Message m = (Message) session.load(Message.class, 1); System.out.println(m.getComments().size()); } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); }
} |
我们发现还是发送了两条sql,并且第二条sql不是我们想象中的count(*)的这种函数查询,而是一个映射查询,这种效率肯定没有我们以前函数的那种查询高。如果我们想使用以前的那种查询,可以在在one-to-many的集合节点上设置lazy属性为extra。
<setname="comments"lazy="extra"> <key>
<column name="mid">column> key> <one-to-many class="Comment"/> set> |
此时查询:
这样可以适当地提示效率,但是还是不建议大家使用hibernate的one-to-many单向。
前面我们学习了many-to-one和one-to-many的单向,我们发现many-to-one更符合我们的程序编写,one-to-many我们大家知道就行了,我们一般不会使用的,但是有时候我们要求严谨性,可能要求使用one-to-many或者many-to-one的双向关联,其实就是many-to-one和one-to-many和在一起的写法。注意这两个的的双向关联是一个意思。下面我们来看看one-to-many的双向关联。
我们还是使用前面的学生和班级的例子,在它的基础上来实现,因为它已经实现了many-to-one的单向,我们现在在班级类中关联学生就实现了双向关联。
Student类不动,ClassRoom类修改如下:
package com.lzcc.hibernate.entity;
import java.util.Set;
publicclass ClassRoom {
privateintid; private Stringname; private Set
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public Set returnstudents; }
publicvoid setStudents(Set this.students = students; }
public ClassRoom() { }
public ClassRoom(int id, String name) { super(); this.id = id; this.name = name; }
@Override public String toString() { return"ClassRoom [id=" +id + ", name=" +name +"]"; } } |
增加Set
Classroom.hbm.xml的写法:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="ClassRoom"table="t_cls_room"> <idname="id"> <generator class="native"/> id> <propertyname="name"/>
<setname="students"lazy="extra"> <key column="cId"/> <one-to-many class="Student"/> set> class> hibernate-mapping> |
下面我们来测试代码:
@Test publicvoid testAdd01() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); /** * 我们先来保存多方 */ Student stu1 = new Student(); stu1.setName("曹操"); stu1.setStuNo("001"); session.save(stu1); Student stu2 = new Student(); stu2.setName("刘备"); stu2.setStuNo("002"); session.save(stu2);
ClassRoom c = new ClassRoom(); c.setName("三国班"); Set stus.add(stu1); stus.add(stu2); c.setStudents(stus); session.save(c); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); }
} |
如果我们先添加一方,再添加多方呢?
@Test publicvoid testAdd02() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); ClassRoom c = new ClassRoom(); c.setName("三国班");
Student stu1 = new Student(); stu1.setName("曹操"); stu1.setStuNo("001"); session.save(stu1); Student stu2 = new Student(); stu2.setName("刘备"); stu2.setStuNo("002"); session.save(stu2);
Set stus.add(stu1); stus.add(stu2); c.setStudents(stus); session.save(c);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
我们发现还同样的问题,当我们实现了一对多的双向关联后,不管我们先添加那方,sql的发送数量总是不理想,会多出几条来完成更新的。
但是我们一定要记住,我们永远不使用一方来维护关系,不管怎么都要使用多方来维护关系就对了,为了这个,hibernate在一方的配置文件的one-to-many节点中提供了一个属性inverse,当我们设置inverse为true时,就是告诉hibernate,我们不再使用一方来维护关系了。
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="ClassRoom"table="t_cls_room"> <idname="id"> <generator class="native"/> id> <propertyname="name"/>
<setname="students"lazy="extra"inverse="true"> <key column="cId"/> <one-to-many class="Student"/> set> class> hibernate-mapping> |
我们使用多方维护关系,测试如下:
@Test publicvoid testAdd03() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); ClassRoom c = new ClassRoom(); c.setName("三国班"); session.save(c);
Student stu1 = new Student(); stu1.setName("曹操"); stu1.setStuNo("001"); //多方维护关系 stu1.setClassRoom(c); session.save(stu1); Student stu2 = new Student(); stu2.setName("刘备"); stu2.setStuNo("002"); //多方维护关系 stu2.setClassRoom(c); session.save(stu2); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
我们发现,如果我们强制规定了有多方维护,此时如果我们还是使用一方维护,则关系没有被创建。如果我们规定了多方维护,我们使用多方维护,则sql只会发送3条,符合我们的想法。
所以最佳实践就是:我们不使用one-to-many单向关联,可以使用many-to-one的单向,也可以使用两者的双向关联,但是使用两者的双向关联时,一定要使用多方来维护一方,一定要记住,当然,具体情况也要根据实际要求来定。
下面我们来看看load方法:
@Test publicvoid testLoad() { Session session = null; try { session = HibernateUtil.openSession();
ClassRoom c = (ClassRoom) session.load(ClassRoom.class, 15); System.out.println(c.getName()); System.out.println(c.getStudents().size());
Student s = (Student) session.load(Student.class, 31); System.out.println(s.getName()); System.out.println(s.getClassRoom().getName());
} catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
我们看出,还是存在延迟加载问题,最后的班级名称没有发送sql,就是因为之前我们已经取出了班级名称,所以sql没有发送,所以延迟加载不一定就是坏事,需要我们根据实际情况决定延迟加载的问题。
one-to-one是一对一的关联,这种案例在现实社会也是最常见的关联,如人和他的身份证或者银行卡,这就是最典型的一对一关联。
One-to-one的单向在那边关联都行。下面我们就以人(Person)和身份证(IDCard)为例。
IDCard.java:
package com.lzcc.hibernate.entity;
publicclass IDCard {
privateintid; private Stringno;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getNo() { returnno; }
publicvoid setNo(String no) { this.no = no; }
public IDCard() { }
public IDCard(String no) { super(); this.no = no; }
public IDCard(int id, String no) { super(); this.id = id; this.no = no; }
@Override public String toString() { return"IDCard [id=" +id + ", no=" +no +"]"; } } |
Person.java:
package com.lzcc.hibernate.entity;
publicclass Person {
privateintid; private Stringname; //表示与IDCard一对一 private IDCardidCard;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public IDCard getIdCard() { returnidCard; }
publicvoid setIdCard(IDCard idCard) { this.idCard = idCard; }
public Person() { }
public Person(int id, String name, IDCard idCard) { super(); this.id = id; this.name = name; this.idCard = idCard; }
public Person(String name, IDCard idCard) { super(); this.name = name; this.idCard = idCard; }
@Override public String toString() { return"Person [id=" +id + ", name=" +name +", idCard=" +idCard + "]"; } } |
IDCard.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="IDCard"table="t_id_card"> <idname="id"> <generator class="native"/> id> <propertyname="no"/> class> hibernate-mapping> |
Person.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Person"table="t_person"> <idname="id"> <generator class="native"/> id> <propertyname="name"/>
<many-to-onename="idCard"column="id_card_id"class="IDCard"unique="true"/> class> hibernate-mapping> |
我们使用many-to-one,加入unique属性等于true,来说明这个是一对一就行了,这就是一对一的配置文件。
将实体类的配置文件加入到hibernate的配置文件中:
<mappingresource="com/lzcc/hibernate/entity/IDCard.hbm.xml"/> <mapping resource="com/lzcc/hibernate/entity/Person.hbm.xml"/> |
使用单元测试来测试代码:
@Test publicvoid testAdd01() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); IDCard card = new IDCard("110120"); session.save(card); //将IDCard设置到用户属性中,表示这个一个一对一的关联 //表示身份证号码为:110120 Person p = new Person("张三", card); session.save(p);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
我们发现运行成功了。
那么我们来测试下,这个是不是真正的一对一呢?
@Test publicvoid testAdd02() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); IDCard card = (IDCard) session.load(IDCard.class, 1); session.save(card); //此时IDCard的id为1的这条数据已经关联了张三这个用户//了,当//我们再去关联一个人 //就会报错,因为这个一对一,不是一对多 Person p = new Person("老刘",card); p.setIdCard(card); session.save(p);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
发现一旦关联一对一,你想使用多条关联就会报错,这样我们的一对一就成功了。这就是一对一的单向关联,不管那边关联都行,注意的是我们使用many-to-one,属性为unique=true来确定one-to-one的单向关联。
one-to-one的双向关联和单向关联很类似,只是在两边的类中同时存在对方的属性字段就行了,还是以上面的例子为案例,我们上面是在Person中已经存在了IDCard的属性段,下面我们修改IDCard类就行了。
IDCard.java:
package com.lzcc.hibernate.entity;
publicclass IDCard {
privateintid; private Stringno; //在IDCard中增加person,完成双向关联 private Personperson;
public Person getPerson() { returnperson; }
publicvoid setPerson(Person person) { this.person = person; }
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getNo() { returnno; }
publicvoid setNo(String no) { this.no = no; }
public IDCard() { }
public IDCard(String no) { super(); this.no = no; }
public IDCard(int id, String no) { super(); this.id = id; this.no = no; }
@Override public String toString() { return"IDCard [id=" +id + ", no=" +no +"]"; } } |
IDCard.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="IDCard"table="t_id_card"> <idname="id"> <generator class="native"/> id> <propertyname="no"/>
<one-to-onename="person"property-ref="idCard"/> class> hibernate-mapping> |
注意:我们在没有维护关系的一方使用one-to-one来说明一对一的双向关联,需要制定property-ref属性,来说明有对端的类中的自己属性的维护关系。
下面测试one-to-one的双向:
@Test publicvoid testAdd01() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); IDCard card = new IDCard("1111111"); session.save(card); Person p = new Person("张帅哥", card); session.save(p); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
我们先添加不维护关系的一方,没有任何问题
那么我们先添加维护关系的一方呢?
@Test publicvoid testAdd02() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); Person p = new Person(); p.setName("张大帅哥"); session.save(p); IDCard card = new IDCard("1111111"); card.setPerson(p); session.save(card); session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
通过运行代码我们知道,如果我们先来完成维护关系一方的添加,虽然代码没有报错,但是关系外键却没有被添加上,所以我们在one-to-one的双向添加时注意:需要先添加没有维护关系的一方,再添加维护关系的一方,这样才能完成对外键的关联。
下面我们来测试one-to-one双向的load,问题就会比较严重了。
@Test publicvoid testLoad01() { Session session = null; try { session = HibernateUtil.openSession(); /** * 我们先来从没有维护关系的一方取数据 */ IDCard card = (IDCard)session.load(IDCard.class, 4); System.out.println(card.getNo()); System.out.println(card.getPerson().getName()); System.out.println(card.getPerson().getIdCard().getNo());
} catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
代码运行效果如下:
使用了一天sql完成了对所有数据的查询,这种状态就是我们最喜欢的状态了,并且我们发现hibernate自动的使用了left join完成了左联接完成数据查询,所以如果使用one-to-one的双向关联,一定要使用没有维护关系(没有外键的一方)的一方去取数据。
Hibernate: select idcard0_.id as id1_2_0_, idcard0_.no as no2_2_0_, person1_.id as id1_4_1_, person1_.name as name2_4_1_, person1_.id_card_id as id_card_3_4_1_ from t_id_card idcard0_left outer join t_person person1_ on idcard0_.id=person1_.id_card_id where idcard0_.id=? |
Hibernate自动的完成了左联接来查询。
如果我们先去查询维护关系的一方(存在外键的一方)呢?
@Test publicvoid testLoad02() { Session session = null; try { session = HibernateUtil.openSession(); //一旦我们使用了维护关系的一方(存在外键的一方)去数据 //我们发现会存在延迟加载问题 Person p = (Person)session.load(Person.class, 3); //发送一条sql查询id为3的person对象 System.out.println(p.getName()); //再发送一条sql查询id为3的person对应的IDCard对象 System.out.println(p.getIdCard().getNo()); //再发送一条sql查询id为3的person对应的IDCard对象对应的Person对象 //本省一条sql可以查询完的,结果发送了3条sql,注意这个问题 System.out.println(p.getIdCard().getPerson().getName());
} catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
注意:我们发现一旦我们使用one-to-one的双向,在从维护关系的一方查询数据,此时存在了延迟加载问题,本身需要一条sql可以完成的查询,结果需要发送3sql来查询,延迟加载导致了N+1事件的触发,一定要记住:我们尽量不要使用one-to-one的双向关联,如果一旦使用了,则一定要在没有维护关系的一方查询数据。
Many-to-many在真实的项目开发中也是很常用的一种关联关系,那么hibernate如何来实现many-to-many的关联关系呢?下面我们来看看,如用户(User)和角色(Role)的关联关系就是多对多,一个用户可以拥有多个角色,一个角色可以分配给多个用户(权限模块中的知识)。
User.java:
package com.lzcc.hibernate.entity;
import java.util.Date; import java.util.Set;
publicclass User {
privateintid; private Stringusername; private Stringpassword; private Stringnickname; private DatebornDate; private Set
public Set returnroles; }
publicvoid setRoles(Set this.roles = roles; }
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getUsername() { returnusername; }
publicvoid setUsername(String username) { this.username = username; }
public String getPassword() { returnpassword; }
publicvoid setPassword(String password) { this.password = password; }
public String getNickname() { returnnickname; }
publicvoid setNickname(String nickname) { this.nickname = nickname; }
public Date getBornDate() { returnbornDate; }
publicvoid setBornDate(Date bornDate) { this.bornDate = bornDate; }
public User() { }
public User(int id, String username, String password, String nickname, Date bornDate) { super(); this.id = id; this.username = username; this.password = password; this.nickname = nickname; this.bornDate = bornDate; }
public User(String username, String password, String nickname, Date bornDate) { super(); this.username = username; this.password = password; this.nickname = nickname; this.bornDate = bornDate; }
@Override public String toString() { return"User [id=" +id + ", username=" +username +", password=" + password + ", nickname=" +nickname +", bornDate=" + bornDate + "]"; } } |
User.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="User"table="t_user"> <idname="id">
<generator class="native"/> id> <propertyname="username"/> <propertyname="password"/> <propertyname="nickname"/> <propertyname="bornDate"type="date"column="born_date"/>
<setname="roles"table="t_user_role">
<key column="uid"/>
<many-to-many class="Role"column="rid"/> set> class> hibernate-mapping> |
Role.java:
package com.lzcc.hibernate.entity;
import java.util.Set;
publicclass Role {
privateintid; private Stringname; private Set
public Set returnusers; }
publicvoid setUsers(Set this.users = users; }
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public Role() { }
public Role(String name) { this.name = name; }
public Role(int id, String name) { super(); this.id = id; this.name = name; }
@Override public String toString() { return"Role [id=" +id + ", name=" +name +"]"; } } |
Role.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Role"table="t_role"> <idname="id"> <generator class="native"/> id> <propertyname="name"/>
<setname="users"table="t_user_role">
<key column="rid"/>
<many-to-many class="User"column="uid"/> set> class> hibernate-mapping> |
这样就完成了多对多的配置,多对多通过对两个类都使用many-to-many配置,相互合作,完成对多对多的配置。
下面我们来测试下吧:
package com.lzcc.hibernate.test; import java.util.HashSet; import java.util.Set; import org.hibernate.Session; import org.junit.Test; import com.lzcc.hibernate.entity.Role; import com.lzcc.hibernate.entity.User; import com.lzcc.hibernate.util.DateUtil; import com.lzcc.hibernate.util.HibernateUtil;
public class TestManyToMany {
@Test public void testAdd01() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); User u1 = new User("ljh", "123", "张三", DateUtil.getDate("1990-10-01")); session.save(u1); User u2 = new User("lsg", "123", "张帅哥", DateUtil.getDate("1990-10-01")); session.save(u2); User u3 = new User("ldsg", "123", "张大帅哥", DateUtil.getDate("1990-10-01")); session.save(u3);
Role r1 = new Role("超级管理员"); Set uses1.add(u1); uses1.add(u2); r1.setUsers(uses1); session.save(r1);
r1 = new Role("普通管理员"); uses1 = new HashSet uses1.add(u2); uses1.add(u3); r1.setUsers(uses1); session.save(r1);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session != null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } } |
我们发现这样使用没有问题,可以正常插入。
下面再测试load方法看看:
@Test publicvoid testLoad() { Session session = null; try { session = HibernateUtil.openSession(); User u = (User) session.load(User.class, 23); System.out.println(u); for (Role r : u.getRoles()) { System.out.println(r); System.out.println(r.getUsers()); }
} catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
代码运行效果如下:
还是存在延迟加载问题,但是查询不是主要问题,我们发现特别是在添加的时候,数据相当麻烦,还有就是,多对多很多时候关系表不仅仅只有外键,还存在其他的一些字段,如Student和课程,在选课系统中,学生可以选多门课程,一门课程可以被多个学生选择,同时我们会在关系表中存储该学生所选该课的成绩,那么这样的关联关系就没办法完成上面的要求了,所以注意:在我们使用了hibernate后,多对多我们一般会分解为两个一对多,这样就可以满足上面的需求了。
上面我们说了,hibernate中使用到多对多时,我们一般会使用两个一对多来实现这个的多对多,下面我们来看看怎么实现吧:
我们以学生和课程为案例讲解。
Student.java:
package com.lzcc.hibernate.entity;
import java.util.Set;
publicclass Student {
privateintid; private Stringname; private StringstuNo; //不建议大家使用双向关联,只是讲课, //需要将全,所以使用了双向关联 private Set
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public String getStuNo() { returnstuNo; }
publicvoid setStuNo(String stuNo) { this.stuNo = stuNo; }
public Set returnscs; }
publicvoid setScs(Set this.scs = scs; }
public Student() { }
public Student(int id, String name, String stuNo, Set super(); this.id = id; this.name = name; this.stuNo = stuNo; this.scs = scs; } public Student(String name, String stuNo, Set super(); this.name = name; this.stuNo = stuNo; this.scs = scs; } } |
Student.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Student"table="t_stu"> <idname="id"> <generator class="native"/> id> <propertyname="name"/> <propertyname="stuNo"/> <setname="scs"lazy="extra"inverse="true"> <key column="stuId"/> <one-to-many class="StudentCourse"/> set> class> hibernate-mapping> |
Coures.java:
package com.lzcc.hibernate.entity; import java.util.Set;
publicclass Course {
privateintid; private Stringname; //不建议大家使用双向关联,只是讲课, //需要将全,所以使用了双向关联 private Set
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
public String getName() { returnname; }
publicvoid setName(String name) { this.name = name; }
public Set returnscs; }
publicvoid setScs(Set this.scs = scs; }
public Course() { }
public Course(int id, String name, Set super(); this.id = id; this.name = name; this.scs = scs; }
public Course(String name, Set super(); this.name = name; this.scs = scs; } } |
Course.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="Course"table="t_course"> <idname="id"> <generator class="native"/> id> <propertyname="name"/> <setname="scs"lazy="extra"inverse="true"> <key column="couseId"/> <one-to-many class="StudentCourse"/> set> class> hibernate-mapping> |
StudentCourse.java:
package com.lzcc.hibernate.entity;
publicclass StudentCourse {
privateintid; privatedoublescore; private Studentstu; private Coursecourse;
publicint getId() { returnid; }
publicvoid setId(int id) { this.id = id; }
publicdouble getScore() { returnscore; }
publicvoid setScore(double score) { this.score = score; }
public Student getStu() { returnstu; }
publicvoid setStu(Student stu) { this.stu = stu; }
public Course getCourse() { returncourse; }
publicvoid setCourse(Course course) { this.course = course; }
public StudentCourse() { }
public StudentCourse(int id,double score, Student stu, Course course) { super(); this.id = id; this.score = score; this.stu = stu; this.course = course; } } |
Student.hbm.xml:
xmlversion="1.0"?> DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mappingpackage="com.lzcc.hibernate.entity"> <classname="StudentCourse"table="t_stu_course"> <idname="id"> <generator class="native"/> id> <propertyname="score"/> <many-to-onename="stu"class="Student"column="stuId"/> <many-to-onename="course"class="Course"column="couseId"/> class> hibernate-mapping> |
Many-to-many,被我们分解为了两个many-to-one,这样做的好吃就是我们可以在第三张关系表中保存一些其他字段的数据。
下面我们来做测试:
@Test publicvoid testAdd01() { Session session = null; try { session = HibernateUtil.openSession(); session.beginTransaction(); Student s1 = new Student(); s1.setName("张三"); s1.setStuNo("001"); session.save(s1);
Student s2 = new Student(); s2.setName("张帅哥"); s2.setStuNo("002"); session.save(s2);
Course c1 = new Course(); c1.setName("Java in Action"); session.save(c1);
Course c2 = new Course(); c2.setName("PHP in Action"); session.save(c2);
StudentCourse tc = new StudentCourse(); tc.setScore(88); tc.setStu(s1); tc.setCourse(c1); session.save(tc);
tc = new StudentCourse(); tc.setScore(99); tc.setStu(s1); tc.setCourse(c2); session.save(tc);
tc = new StudentCourse(); tc.setScore(100); tc.setStu(s2); tc.setCourse(c2); session.save(tc);
tc = new StudentCourse(); tc.setScore(60); tc.setStu(s2); tc.setCourse(c1); session.save(tc);
session.getTransaction().commit(); } catch (Exception e) { e.printStackTrace(); if (session !=null) { session.getTransaction().rollback(); } } finally { HibernateUtil.closed(session); } } |
运行代码:
下面测试load方法:
@Test publicvoid testLoad() { Session session = null; try { session = HibernateUtil.openSession(); Student s = (Student) session.load(Student.class, 2); System.out.println(s); for (StudentCourse c : s.getScs()) { System.out.println(c); System.out.println(c.getCourse().getName()); } } catch (Exception e) { e.printStackTrace(); } finally { HibernateUtil.closed(session); } } |
我们发现还是因为延迟加载问题,本身需要一条sql就可以完成的查询,它发送了大量的sql去取数据,而且我们一般会在一方取多方的时候,会涉及到分页,不建议大家使用双向关联的。
1、 在hibernate中,能够使用单向关联,就不使用双向关联,不是特殊情况,不使用双向关联。
2、 使用单向关联时,一定要在多方维护关系。
3、 多对多时,使用两个一对多完成,并且也一定要使用单向关联。