- 浏览: 27860 次
- 性别:
- 来自: 北京
文章分类
最新评论
一:一对多
实体类:
hbm.xml配置
二:多对一
实体是Employee和Department,它们之间是多对一的关系。
Department类:
Department.hbm.xml:
Employee.hbm.xml
many-to-one没有inverse属性,因为关系的维护是many的一方,不可能放弃对关系的维护。
many-to-one的lazy属性有三个取值:false, proxy, no-proxy。
1.测试cascade属性:
结果是报org.hibernate.TransientObjectException异常,因为没有保存Department实例。
可以加cascade属性,解决问题:
2.测试fetch属性:
查询语句如下:
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.department as department1_0_, employee0_.skill as skill1_0_, employee0_.sell as sell1_0_, employee0_.type as type1_0_ from Employee employee0_ where employee0_.id=?
Hibernate: select department0_.id as id0_0_, department0_.name as name0_0_ from Department department0_ where department0_.id=?
因为fetch设置为select,所以对每个实体,都分别用一个SELECT语句
如果把fetch设置为join,也就是连表查询,只使用一个SELECT语句。如下:
Hibernate: select employee0_.id as id1_1_, employee0_.name as name1_1_, employee0_.department as department1_1_, employee0_.skill as skill1_1_, employee0_.sell as sell1_1_, employee0_.type as type1_1_, department1_.id as id0_0_, department1_.name as name0_0_ from Employee employee0_ left outer join Department department1_ on employee0_.department=department1_.id where employee0_.id=?
3.测试lazy属性:
当fetch为select时,设置lazy为proxy或者no-proxy。
测试代码:
结果是报org.hibernate.LazyInitializationException异常。
因为fetch为select,而且lazy为proxy或者no-proxy,所以开始仅仅查询Employee,当需要用SELECT语句查询Department时,Session已经关闭。
解决办法:
1. 设置lazy为false,hibernate会第一时间把Employee和Department查询出来。
如果fetch为select,使用两个SELECT查询语句。
如果fetch为join,使用一个SELECT连表查询语句。
2. 设置fetch为join,这时不管lazy的取值,hibernate会进行连表查询,把两个实体都查询出来。
三:多对多
多对多(many-to-many):在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;hibernate会为我们创建中间关联表,转换成两个一对多。
E-R图
Teacher类:
Student类:
Teacher.hbm.xml:
Student.hbm.xml:
一定要注意映射文件中<many-to-many class="Teacher" column="teacher_id"/>中class的值,它必须与你另一个关联映射文件中的class属性的name值一致,其实就是与你的实体类的类名一致,如:<many-to-many class="Teacher" column="teacher_id"/>中class的值就不能写成"teacher"。如果写成这样的话,就会抛出如下异常:An association from the table teacher_student refers to an unmapped class: com.reiyen .hibernate.domain.teacher
测试代码:
运行此程序后:控制台打印的sql语句如下所示:
Hibernate: insert into Teacher (name) values (?)
Hibernate: insert into Teacher (name) values (?)
Hibernate: insert into Student (name) values (?)
Hibernate: insert into Student (name) values (?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
一共在中间表里面插入了4条记录。
中间表结构如下所示:
DROP TABLE IF EXISTS `test`.`teacher_student`;
CREATE TABLE `test`.`teacher_student` (
`teacher_id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
PRIMARY KEY (`student_id`,`teacher_id`),
KEY `FK2E2EF2DE6C8A2663` (`teacher_id`),
KEY `FK2E2EF2DE5BEEDBC3` (`student_id`),
CONSTRAINT `FK2E2EF2DE5BEEDBC3` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`),
CONSTRAINT `FK2E2EF2DE6C8A2663` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
表中插入的记录如下所示:
mysql> select * from teacher_student;
+------------+------------+
| teacher_id | student_id |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
+------------+------------+
4 rows in set (0.00 sec)
程序中注释为1的语句非常重要,它是建立Teacher与Student关联的语句,如果没有这两条语句,虽然程序照样会执行,但是在中间表teacher_student没有任何记录,也就是Teacher与Student之间未关联。
当然你也可以通过程序中注释为2的语句来建立Teacher与Student之间的关联关系,同样会产生与注释为1的语句的效果。但是你不能在程序中同时出现以上四句程序,否则会抛出异常( PRIMARY KEY (`student_id`,`teacher_id`),所以会出现主键冲突的异常 ),:
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Exception in thread "main" org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
解决上面产生异常的办法是设置inverse属性。即在Tearcher一端或Student一端设置inverse="true",即让他们之中的某一方放弃维护关联关系。此时,虽然上面四句程序在测试程序中同时出现了(其实就是在对象模型上相互设置了他们的关联关系),但程序照样能运行正常,因为了在数据库模型上,只会有两句程序生效,也就是没有设置inverse="true"的那一端会去维护关联关系。有关inverse的说细信息,可以参看我的文章hibernate级联(cascade和inverse).
执行测试程序中的查询测试,控制台打印的信息如下所示:
Hibernate: select teacher0_.id as id5_0_, teacher0_.name as name5_0_ from Teacher teacher0_ where teacher0_.id=?
Hibernate: select students0_.teacher_id as teacher1_1_, students0_.student_id as student2_1_, student1_.id as id7_0_, student1_.name as name7_0_ from teacher_student students0_ left outer join Student student1_ on students0_.student_id=student1_.id where students0_.teacher_id=?
students:2
从打印出的sql语句可以看出,多对多关系进行查询时,效率是比较低的。
四:一对一
一对一(one-to-one)实例(Person-IdCard)
一对一的关系在数据库中表示为主外关系.例如.人和身份证的关系.每个人都对应一个身份证号.我们应该两个表.一个是关于人信息的表(Person).别外一个是身份证相关信息的表(id_card).id_card表的主键对应该Person表的主键id,也是Person表的外键.有人才能有身份证.所以此例中Person是主表,id_card表为从表。
hibernate的一对一关系有两种形式,一种是共享主键方式,另一种是唯一外键方式.
<一>、共享主键方式实现一对一
1. 实体类设计如下:
Person类:
Idcard类:
Person.hbm.xml:
IdCard.hbm.xml:
3.测试代码:
控制台打印信息如下所示:
Hibernate: insert into Person (name) values (?)
Hibernate: insert into id_card (authorize_date, id) values (?, ?)
person name : person1
数据库表id_card的创建语句如下所示:(重点注意红色字体部分)
DROP TABLE IF EXISTS `test`.`id_card`;
CREATE TABLE `test`.`id_card` (
`id` int(11) NOT NULL,
`authorize_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK627C1FB4284AAF67` (`id`),
CONSTRAINT `FK627C1FB4284AAF67` FOREIGN KEY (`id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据库中记录如下所示:
mysql> select * from person;
+----+---------+
| id | name |
+----+---------+
| 1 | person1 |
+----+---------+
1 row in set (0.00 sec)
mysql> select * from id_card;
+----+---------------------+
| id | authorize_date |
+----+---------------------+
| 1 | 2010-03-23 01:07:25 |
+----+---------------------+
1 row in set (0.00 sec)
4.查询测试:
执行此测试类时,控制台打印信息如下所示:
Hibernate: select person0_.id as id3_1_, person0_.name as name3_1_, idcard1_.id as id4_0_, idcard1_.authorize_date as authorize2_4_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
2010-03-23 21:46:07.0
从打印的SQL语句可以看出,一对一关系查询主对象时,然后得到主对象中的从属对象,通过left join一次就把查询结果查询出来了,因为从对象从属于主对象。而一对多,多对一关系时,从打印的SQL语句可知,它经过了两次查询才将查询结果查询出来。这是它们的区别。如果一对一关系中先查询从属对象,然后得到从属中的主对象时(即把上面测试类中的注释1, 2都去掉再运行),控制台打印信息如下:
Hibernate: select idcard0_.id as id4_0_, idcard0_.authorize_date as authorize2_4_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id3_1_, person0_.name as name3_1_, idcard1_.id as id4_0_, idcard1_.authorize_date as authorize2_4_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
person1
从打印的SQL可以看出,这种先查从对象,然后得到从属对象中的主对象,要经过两次查询才能得到查询结果。
如果只把测试程序中注释1去掉运行,则只会执行上面两次查询中的第一次查询。
6.one-to-one(元素)懒加载分析:
必须同时满足下面的三个条件时才能实现懒散加载:1).lazy!=false (lazy缺省方式就!=false)2).constrained=true 3).fetch=select(fetch缺省方式即为select)
(因为主表不能有constrained=true,所以主表没有懒加载功能)。能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外)时,hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象;当相关联的session关闭后,再访问懒加载的对象将会出现异常。
测试:(1).注释掉查询测试程序中标记为3的语句,运行程序,控制台打印信息如下:
Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
说明查询主对象Person时没有懒加载特性,因此它通过left outer join id_card表,同时把它的从对象IdCard也查询出来了。那为什么一对一中查询主对象时,不能实现懒加载呢??大家可以看看person表的结构,你从表结构中根本不能决断出Person对象有没有相应的IdCard对象,所以它无法给setIdCard赋值(hibernate不能想当然的认为你有值,给你new一个代理对象给它),所以它一定要去查询相关联的对象表,看是否有与此Person对应的IdCard记录。而如果查询的是从对象IdCard时,因为idcard中的id是一个person表的一个外键,所以它必定有一个相对应的Person对象(因为有constrained=true),所以它可以先返回给你一个代理对象,当你真正需要Person对象的数据时,它再去查询数据库。
(2).注释掉查询测试程序中标记为3,4的语句,同进将标记为1的语句前的注释去掉再运行程序,控制台打印信息如下:
Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
从打印信息可以看出,查询从对象IdCard时实现了懒加载功能,因为它只查询了IdCard对象,而关联的Person对象它没有进行查询。
(3).如果在(2)基础上将标记为2的语句前的注释也去掉再运行程序,控制台打印信息如下:
Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
person1
它就进行了两次查询,将IdCard关联的Person对象也进行了查询。因为访问这些懒加载对象(代理对象)的属性(getId和getClass除外)时,hibernate会初始化这些代理.
(4).如果修改IdCard.hbm.xml映射文件,增加fetch="join",如下所示:
xml代码:
再按(2)进行测试,此时控制台打印信息如下:
Hibernate: select idcard0_.id as id5_1_, idcard0_.authorize_date as authorize2_5_1_, person1_.id as id4_0_, person1_.name as name4_0_ from id_card idcard0_ inner join Person person1_ on idcard0_.id=person1_.id where idcard0_.id=?
此时查询从对象IdCard时也不再懒加载了,通过inner join一次性将主从对象都查询出来。
(5).如果修改IdCard.hbm.xml映射文件,增加lazy="false",如下所示:
xml代码:
再按(2)进行测试,此时控制台打印信息如下:
Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
虽然也不实现懒加载功能,一次性将主从对象都查询出来,但此时是经过两次查询才得到结果。
如果修改IdCard.hbm.xml映射文件,增加lazy="proxy",如下所示,与缺省时一样的效果,因为缺省时,lazy是=proxy
xml代码:
如果修改IdCard.hbm.xml映射文件,如下所示,则lazy(懒加载失效),此时效果如测试(4)。
xml代码:
<二>、唯一外键方式实现一对一
基于外键的one-to-one可以描述为多对一。
hibernate 一对一唯一外键关联映射(双向关联 Person<---->IdCard )
一对一唯一外键 双向 关联,需要在另一端(person ),添加 <one-to-one> 标签,指示 hibernate 如何加载
其关联对象,默认根据主键加载idcard ,外键关联映射中,因为两个实体采用的是 idcard 的外键维护的关系, 所以不能指定主键加载 idcard ,而要根据 idcard 的外键加载,所以采用如下映射方式:
<one-to-one name="idcard" property-ref="person"/>
IdCard.hbm.xml的映射文件如下:
Person.hbm.xml的映射文件如下:
实体类不用修改,还是用上面的测试类进行测试即可。
保存测试类运行后,相对共享主键方式的one-to-one,id_card表的结构发生了变化,表结构如下所示:
DROP TABLE IF EXISTS `test`.`id_card`;
CREATE TABLE `test`.`id_card` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`authorize_date` datetime DEFAULT NULL,
`person_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `person_id` (`person_id`) ,
KEY `FK627C1FB45B253C91` (`person_id`),
CONSTRAINT `FK627C1FB45B253C91` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
数据库表中记录如下:
mysql> select * from person;
+----+---------+
| id | name |
+----+---------+
| 1 | person1 |
+----+---------+
1 row in set (0.00 sec)
mysql> select * from id_card;
+----+---------------------+-----------+
| id | authorize_date | person_id |
+----+---------------------+-----------+
| 1 | 2010-03-23 22:40:38 | 1 |
+----+---------------------+-----------+
1 row in set (0.00 sec)
如果Person.hbm.xml映射文件中没有<one-to-one/>这一项的话,运行测试:
会抛出如下异常:
java.lang.NullPointerException
因为这种关系成了IdCard--->Person的单向关联了。知道了Person,找不到对应的IdCard.
当运行如下测试时:
控制台会打印出Person相对应的IdCard为null.
但如果得到了IdCard,却能找到相应的Person.测试如下:
能得到正常的结果,person name为person1.
总结: 在缺省情况下,hibernate只有在一对一关联中,查询主对象时,是进行关联查询一次得到查询结果,其它(多对多、多对一、一对多、一对一查询从对象)的查询都是分两次查询得到查询结果。
实体类:
package cn.cat.model; public class User { private int id; private String name; Private Set<Order> orders = new HashSet<Order>(); //setter & getter ...... } public class Order{ private int id; private String orderName; private int user_id; //setter & getter..... }
hbm.xml配置
<property name="name" type="string" column="name"> <set name="orders" cascade="true" lazy="false" fetch="join"> <!--指定关联的外键--> <key column="user_id"/> <one-to-many class="cc. cn.cat.model.Order"/> </set>
二:多对一
实体是Employee和Department,它们之间是多对一的关系。
Department类:
public class Department { private int id; private String name; public Department() { } public Department(String name) { this.name = name; } // getters and setters are omitted } Employee类:
public class Employee { private int id; private String name; private Department department; public Employee() { } public Employee(String name) { this.name = name; } // getters and setters are omitted
Department.hbm.xml:
<hibernate-mapping package="com.john.myhibernate.domain"> <class name="Department"> <id name="id"> <generator class="native"/> </id> <property name="name" length="20" not-null="true"/> </class> </hibernate-mapping>
Employee.hbm.xml
<hibernate-mapping package="com.john.myhibernate.domain"> <class name="Employee"> <id name="id"> <generator class="native"/> </id> <property name="name" length="20" not-null="true"/> <many-to-one name="department" column="department_id" class="Department" fetch="select"/> </class> </hibernate-mapping>
many-to-one没有inverse属性,因为关系的维护是many的一方,不可能放弃对关系的维护。
many-to-one的lazy属性有三个取值:false, proxy, no-proxy。
1.测试cascade属性:
public void testSaveCascade() { Session s = null; Transaction tx = null; Department depart = new Department(); depart.setName("FCI"); Employee em1 = new Employee("John"); em1.setDepartment(depart); Employee em2 = new Employee("Lucy"); em2.setDepartment(depart); try { s = HibernateUtil.getSession(); tx = s.beginTransaction(); s.save(em1); s.save(em2); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally { if (s != null) s.close(); } }
结果是报org.hibernate.TransientObjectException异常,因为没有保存Department实例。
可以加cascade属性,解决问题:
<many-to-one name="department" column="department_id" class="Department" fetch="select" cascade="save-update"/>
2.测试fetch属性:
Session s = null; s = HibernateUtil.getSession(); Employee em = (Employee) s.get(Employee.class, 2); System.out.println(em.getName()); System.out.println(em.getDepartment());
查询语句如下:
Hibernate: select employee0_.id as id1_0_, employee0_.name as name1_0_, employee0_.department as department1_0_, employee0_.skill as skill1_0_, employee0_.sell as sell1_0_, employee0_.type as type1_0_ from Employee employee0_ where employee0_.id=?
Hibernate: select department0_.id as id0_0_, department0_.name as name0_0_ from Department department0_ where department0_.id=?
因为fetch设置为select,所以对每个实体,都分别用一个SELECT语句
如果把fetch设置为join,也就是连表查询,只使用一个SELECT语句。如下:
Hibernate: select employee0_.id as id1_1_, employee0_.name as name1_1_, employee0_.department as department1_1_, employee0_.skill as skill1_1_, employee0_.sell as sell1_1_, employee0_.type as type1_1_, department1_.id as id0_0_, department1_.name as name0_0_ from Employee employee0_ left outer join Department department1_ on employee0_.department=department1_.id where employee0_.id=?
3.测试lazy属性:
当fetch为select时,设置lazy为proxy或者no-proxy。
<many-to-one name="department" column="department_id" class="Department" fetch="select" cascade="save-update" lazy="no-proxy"/>
测试代码:
Session s = null; s = HibernateUtil.getSession(); Employee em = (Employee) s.get(Employee.class, 2); s.close(); System.out.println(em.getName()); System.out.println(em.getDepartment());
结果是报org.hibernate.LazyInitializationException异常。
因为fetch为select,而且lazy为proxy或者no-proxy,所以开始仅仅查询Employee,当需要用SELECT语句查询Department时,Session已经关闭。
解决办法:
1. 设置lazy为false,hibernate会第一时间把Employee和Department查询出来。
如果fetch为select,使用两个SELECT查询语句。
如果fetch为join,使用一个SELECT连表查询语句。
2. 设置fetch为join,这时不管lazy的取值,hibernate会进行连表查询,把两个实体都查询出来。
三:多对多
多对多(many-to-many):在操作和性能方面都不太理想,所以多对多的映射使用较少,实际使用中最好转换成一对多的对象模型;hibernate会为我们创建中间关联表,转换成两个一对多。
E-R图
Teacher类:
package com.reiyen.hibernate.domain; import java.util.Set; public class Teacher { private int id; private String name; private Set<Student> students; //setter和getter方法 }
Student类:
package com.reiyen.hibernate.domain; import java.util.Set; public class Student { private int id; private String name; private Set<Teacher> teachers; //setter和getter方法 }
Teacher.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.reiyen.hibernate.domain"> <class name="Teacher"> <id name="id"> <generator class="native" /> </id> <property name="name" /> <!-- 通过table项告诉hibernate中间表的名称 --> <set name="students" table="teacher_student"> <!-- 通过key属性告诉hibernate在中间表里面查询teacher_id值相应的teacher记录 --> <key column="teacher_id" /> <!-- 通过column项告诉hibernate对student表中查找student_id值相就的studnet记录 --> <many-to-many class="Student" column="student_id" /> </set> </class> </hibernate-mapping>
Student.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.reiyen.hibernate.domain"> <class name="Student" > <id name="id" > <generator class="native" /> </id> <property name="name" /> <set name="teachers" table="teacher_student"> <key column="student_id" /> <many-to-many class="Teacher" column="teacher_id"/> </set> </class> </hibernate-mapping>
一定要注意映射文件中<many-to-many class="Teacher" column="teacher_id"/>中class的值,它必须与你另一个关联映射文件中的class属性的name值一致,其实就是与你的实体类的类名一致,如:<many-to-many class="Teacher" column="teacher_id"/>中class的值就不能写成"teacher"。如果写成这样的话,就会抛出如下异常:An association from the table teacher_student refers to an unmapped class: com.reiyen .hibernate.domain.teacher
测试代码:
public class Many2Many { public static void main(String[] args) { add(); //query(1); } static void query(int id) { Session s = null; Transaction tx = null; try { s = HibernateUtil.getSession(); tx = s.beginTransaction(); Teacher t = (Teacher) s.get(Teacher.class, id); System.out.println("students:" + t.getStudents().size()); tx.commit(); } finally { if (s != null) s.close(); } } static void add() { Session s = null; Transaction tx = null; try { Set<Teacher> ts = new HashSet<Teacher>(); Teacher t1 = new Teacher(); t1.setName("t1 name"); ts.add(t1); Teacher t2 = new Teacher(); t2.setName("t2 name"); ts.add(t2); Set<Student> ss = new HashSet<Student>(); Student s1 = new Student(); s1.setName("s1"); ss.add(s1); Student s2 = new Student(); s2.setName("s2"); ss.add(s2); t1.setStudents(ss); //1 t2.setStudents(ss); //1 // // s1.setTeachers(ts); //2 // s2.setTeachers(ts); //2 s = HibernateUtil.getSession(); tx = s.beginTransaction(); s.save(t1); s.save(t2); s.save(s1); s.save(s2); tx.commit(); } finally { if (s != null) s.close(); } } }
运行此程序后:控制台打印的sql语句如下所示:
Hibernate: insert into Teacher (name) values (?)
Hibernate: insert into Teacher (name) values (?)
Hibernate: insert into Student (name) values (?)
Hibernate: insert into Student (name) values (?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
一共在中间表里面插入了4条记录。
中间表结构如下所示:
DROP TABLE IF EXISTS `test`.`teacher_student`;
CREATE TABLE `test`.`teacher_student` (
`teacher_id` int(11) NOT NULL,
`student_id` int(11) NOT NULL,
PRIMARY KEY (`student_id`,`teacher_id`),
KEY `FK2E2EF2DE6C8A2663` (`teacher_id`),
KEY `FK2E2EF2DE5BEEDBC3` (`student_id`),
CONSTRAINT `FK2E2EF2DE5BEEDBC3` FOREIGN KEY (`student_id`) REFERENCES `student` (`id`),
CONSTRAINT `FK2E2EF2DE6C8A2663` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
表中插入的记录如下所示:
mysql> select * from teacher_student;
+------------+------------+
| teacher_id | student_id |
+------------+------------+
| 1 | 1 |
| 1 | 2 |
| 2 | 1 |
| 2 | 2 |
+------------+------------+
4 rows in set (0.00 sec)
程序中注释为1的语句非常重要,它是建立Teacher与Student关联的语句,如果没有这两条语句,虽然程序照样会执行,但是在中间表teacher_student没有任何记录,也就是Teacher与Student之间未关联。
当然你也可以通过程序中注释为2的语句来建立Teacher与Student之间的关联关系,同样会产生与注释为1的语句的效果。但是你不能在程序中同时出现以上四句程序,否则会抛出异常( PRIMARY KEY (`student_id`,`teacher_id`),所以会出现主键冲突的异常 ),:
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (teacher_id, student_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Hibernate: insert into teacher_student (student_id, teacher_id) values (?, ?)
Exception in thread "main" org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
解决上面产生异常的办法是设置inverse属性。即在Tearcher一端或Student一端设置inverse="true",即让他们之中的某一方放弃维护关联关系。此时,虽然上面四句程序在测试程序中同时出现了(其实就是在对象模型上相互设置了他们的关联关系),但程序照样能运行正常,因为了在数据库模型上,只会有两句程序生效,也就是没有设置inverse="true"的那一端会去维护关联关系。有关inverse的说细信息,可以参看我的文章hibernate级联(cascade和inverse).
执行测试程序中的查询测试,控制台打印的信息如下所示:
Hibernate: select teacher0_.id as id5_0_, teacher0_.name as name5_0_ from Teacher teacher0_ where teacher0_.id=?
Hibernate: select students0_.teacher_id as teacher1_1_, students0_.student_id as student2_1_, student1_.id as id7_0_, student1_.name as name7_0_ from teacher_student students0_ left outer join Student student1_ on students0_.student_id=student1_.id where students0_.teacher_id=?
students:2
从打印出的sql语句可以看出,多对多关系进行查询时,效率是比较低的。
四:一对一
一对一(one-to-one)实例(Person-IdCard)
一对一的关系在数据库中表示为主外关系.例如.人和身份证的关系.每个人都对应一个身份证号.我们应该两个表.一个是关于人信息的表(Person).别外一个是身份证相关信息的表(id_card).id_card表的主键对应该Person表的主键id,也是Person表的外键.有人才能有身份证.所以此例中Person是主表,id_card表为从表。
hibernate的一对一关系有两种形式,一种是共享主键方式,另一种是唯一外键方式.
<一>、共享主键方式实现一对一
1. 实体类设计如下:
Person类:
package com.reiyen.hibernate.domain; public class Person { private int id; private String name; private IdCard idCard; //setter和getter方法 }
Idcard类:
package com.reiyen.hibernate.domain; public class IdCard { private int id; private Date authorizeDate; private Person person; //setter和getter方法 }
Person.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.reiyen.hibernate.domain"> <class name="Person" > <id name="id" > <generator class="native" /> </id> <property name="name" /> <one-to-one name="idCard" /> </class> </hibernate-mapping>
IdCard.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.reiyen.hibernate.domain"> <class name="IdCard" table="id_card"> <id name="id"> <!-- id_card的主键来源person,也就是共享idCard的主键 --> <generator class="foreign"> <param name="property">person</param> </generator> </id> <property name="authorizeDate" column="authorize_date" /> <!-- one-to-one标签的含义,指示hibernate怎么加载它的关联对象,默认根据主键加载, constrained="true", 表明当前主键上存在一个约束,id_card的主键作为外键参照了person --> <one-to-one name="person" constrained="true"></one-to-one> </class> </hibernate-mapping>
3.测试代码:
public class One2One { public static void main(String[] args) { Person person = add(); System.out.println("peron name:" + person.getName()); } static Person add(){ Session session = null; Transaction tran = null; try { session = HibernateUtil.getSession(); IdCard idCard = new IdCard(); idCard.setAuthorizeDate(new Date()); Person p = new Person(); p.setName("person1"); p.setIdCard(idCard); // 1 idCard.setPerson(p); // 2 tran = session.beginTransaction(); session.save(p); session.save(idCard); tran.commit(); return p; } finally { if (session != null) session.close(); }
控制台打印信息如下所示:
Hibernate: insert into Person (name) values (?)
Hibernate: insert into id_card (authorize_date, id) values (?, ?)
person name : person1
数据库表id_card的创建语句如下所示:(重点注意红色字体部分)
DROP TABLE IF EXISTS `test`.`id_card`;
CREATE TABLE `test`.`id_card` (
`id` int(11) NOT NULL,
`authorize_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK627C1FB4284AAF67` (`id`),
CONSTRAINT `FK627C1FB4284AAF67` FOREIGN KEY (`id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
数据库中记录如下所示:
mysql> select * from person;
+----+---------+
| id | name |
+----+---------+
| 1 | person1 |
+----+---------+
1 row in set (0.00 sec)
mysql> select * from id_card;
+----+---------------------+
| id | authorize_date |
+----+---------------------+
| 1 | 2010-03-23 01:07:25 |
+----+---------------------+
1 row in set (0.00 sec)
4.查询测试:
public class One2One { public static void main(String[] args) { query(1); } static void query(Integer id){ Session session = null; try { session = HibernateUtil.getSession(); Person person = (Person)session.get(Person.class, id);//4 System.out.println(person.getIdCard().getAuthorizeDate());//3 //IdCard idCard = (IdCard)session.get(IdCard.class, id); //1 //System.out.println(idCard.getPerson().getName()); //2 } finally { if (session != null) session.close(); } } }
执行此测试类时,控制台打印信息如下所示:
Hibernate: select person0_.id as id3_1_, person0_.name as name3_1_, idcard1_.id as id4_0_, idcard1_.authorize_date as authorize2_4_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
2010-03-23 21:46:07.0
从打印的SQL语句可以看出,一对一关系查询主对象时,然后得到主对象中的从属对象,通过left join一次就把查询结果查询出来了,因为从对象从属于主对象。而一对多,多对一关系时,从打印的SQL语句可知,它经过了两次查询才将查询结果查询出来。这是它们的区别。如果一对一关系中先查询从属对象,然后得到从属中的主对象时(即把上面测试类中的注释1, 2都去掉再运行),控制台打印信息如下:
Hibernate: select idcard0_.id as id4_0_, idcard0_.authorize_date as authorize2_4_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id3_1_, person0_.name as name3_1_, idcard1_.id as id4_0_, idcard1_.authorize_date as authorize2_4_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
person1
从打印的SQL可以看出,这种先查从对象,然后得到从属对象中的主对象,要经过两次查询才能得到查询结果。
如果只把测试程序中注释1去掉运行,则只会执行上面两次查询中的第一次查询。
6.one-to-one(元素)懒加载分析:
必须同时满足下面的三个条件时才能实现懒散加载:1).lazy!=false (lazy缺省方式就!=false)2).constrained=true 3).fetch=select(fetch缺省方式即为select)
(因为主表不能有constrained=true,所以主表没有懒加载功能)。能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外)时,hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象;当相关联的session关闭后,再访问懒加载的对象将会出现异常。
测试:(1).注释掉查询测试程序中标记为3的语句,运行程序,控制台打印信息如下:
Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
说明查询主对象Person时没有懒加载特性,因此它通过left outer join id_card表,同时把它的从对象IdCard也查询出来了。那为什么一对一中查询主对象时,不能实现懒加载呢??大家可以看看person表的结构,你从表结构中根本不能决断出Person对象有没有相应的IdCard对象,所以它无法给setIdCard赋值(hibernate不能想当然的认为你有值,给你new一个代理对象给它),所以它一定要去查询相关联的对象表,看是否有与此Person对应的IdCard记录。而如果查询的是从对象IdCard时,因为idcard中的id是一个person表的一个外键,所以它必定有一个相对应的Person对象(因为有constrained=true),所以它可以先返回给你一个代理对象,当你真正需要Person对象的数据时,它再去查询数据库。
(2).注释掉查询测试程序中标记为3,4的语句,同进将标记为1的语句前的注释去掉再运行程序,控制台打印信息如下:
Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
从打印信息可以看出,查询从对象IdCard时实现了懒加载功能,因为它只查询了IdCard对象,而关联的Person对象它没有进行查询。
(3).如果在(2)基础上将标记为2的语句前的注释也去掉再运行程序,控制台打印信息如下:
Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
person1
它就进行了两次查询,将IdCard关联的Person对象也进行了查询。因为访问这些懒加载对象(代理对象)的属性(getId和getClass除外)时,hibernate会初始化这些代理.
(4).如果修改IdCard.hbm.xml映射文件,增加fetch="join",如下所示:
xml代码:
<one-to-one name="person" constrained="true" fetch="join"/>
再按(2)进行测试,此时控制台打印信息如下:
Hibernate: select idcard0_.id as id5_1_, idcard0_.authorize_date as authorize2_5_1_, person1_.id as id4_0_, person1_.name as name4_0_ from id_card idcard0_ inner join Person person1_ on idcard0_.id=person1_.id where idcard0_.id=?
此时查询从对象IdCard时也不再懒加载了,通过inner join一次性将主从对象都查询出来。
(5).如果修改IdCard.hbm.xml映射文件,增加lazy="false",如下所示:
xml代码:
<one-to-one name="person" constrained="true" lazy="false" />
再按(2)进行测试,此时控制台打印信息如下:
Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
虽然也不实现懒加载功能,一次性将主从对象都查询出来,但此时是经过两次查询才得到结果。
如果修改IdCard.hbm.xml映射文件,增加lazy="proxy",如下所示,与缺省时一样的效果,因为缺省时,lazy是=proxy
xml代码:
<one-to-one name="person" constrained="true" lazy="proxy" />
如果修改IdCard.hbm.xml映射文件,如下所示,则lazy(懒加载失效),此时效果如测试(4)。
xml代码:
<one-to-one name="person" constrained="true" lazy="proxy" fetch="join"/>
<二>、唯一外键方式实现一对一
基于外键的one-to-one可以描述为多对一。
hibernate 一对一唯一外键关联映射(双向关联 Person<---->IdCard )
一对一唯一外键 双向 关联,需要在另一端(person ),添加 <one-to-one> 标签,指示 hibernate 如何加载
其关联对象,默认根据主键加载idcard ,外键关联映射中,因为两个实体采用的是 idcard 的外键维护的关系, 所以不能指定主键加载 idcard ,而要根据 idcard 的外键加载,所以采用如下映射方式:
<one-to-one name="idcard" property-ref="person"/>
IdCard.hbm.xml的映射文件如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.itcast.hibernate.domain"> <class name="IdCard" table="id_card"> <id name="id"> <generator class="native" /> </id> <property name="authorizeDate" column="authorize_date" /> <!-- 指定多的一端的unique=true,这样就限制了多的一端的多重性为一 通过这种手段映射一对一唯一外键关联 --> <many-to-one name="person" column="person_id" unique="true" /> </class> </hibernate-mapping>
Person.hbm.xml的映射文件如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.itcast.hibernate.domain"> <class name="Person" > <id name="id" > <generator class="native" /> </id> <property name="name" /> <!-- 没有下面的one-to-one标签也行,但那样就变成了单向关联(IdCard ----》 Person) ,也就是当知道IdCard后,能找到它属于的对应的人,但知道某人后,却无法找到相对应的IdCard--> <one-to-one name="idCard" property-ref="person"/> </class> </hibernate-mapping>
实体类不用修改,还是用上面的测试类进行测试即可。
保存测试类运行后,相对共享主键方式的one-to-one,id_card表的结构发生了变化,表结构如下所示:
DROP TABLE IF EXISTS `test`.`id_card`;
CREATE TABLE `test`.`id_card` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`authorize_date` datetime DEFAULT NULL,
`person_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `person_id` (`person_id`) ,
KEY `FK627C1FB45B253C91` (`person_id`),
CONSTRAINT `FK627C1FB45B253C91` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
数据库表中记录如下:
mysql> select * from person;
+----+---------+
| id | name |
+----+---------+
| 1 | person1 |
+----+---------+
1 row in set (0.00 sec)
mysql> select * from id_card;
+----+---------------------+-----------+
| id | authorize_date | person_id |
+----+---------------------+-----------+
| 1 | 2010-03-23 22:40:38 | 1 |
+----+---------------------+-----------+
1 row in set (0.00 sec)
如果Person.hbm.xml映射文件中没有<one-to-one/>这一项的话,运行测试:
Person person = (Person)session.get(Person.class, id); System.out.println(person.getIdCard().getAuthorizeDate());
会抛出如下异常:
java.lang.NullPointerException
因为这种关系成了IdCard--->Person的单向关联了。知道了Person,找不到对应的IdCard.
当运行如下测试时:
Person person = (Person)session.get(Person.class, id); System.out.println(person.getIdCard());
控制台会打印出Person相对应的IdCard为null.
但如果得到了IdCard,却能找到相应的Person.测试如下:
IdCard idCard = (IdCard)session.get(IdCard.class, id); System.out.println(idCard.getPerson().getName());
能得到正常的结果,person name为person1.
总结: 在缺省情况下,hibernate只有在一对一关联中,查询主对象时,是进行关联查询一次得到查询结果,其它(多对多、多对一、一对多、一对一查询从对象)的查询都是分两次查询得到查询结果。
相关推荐
在本篇中,我们将深入探讨Hibernate的配置,特别是涉及一对一、一对多和多对多关系的配置。 首先,配置过程始于`Configuration`接口。这个接口用于设置Hibernate所需的配置信息,如数据源、连接参数等,并根据配置...
Hibernate作为一个优秀的对象关系映射(ORM)框架,极大地简化了数据库操作。在这个场景中,我们关注的是Hibernate的配置文件——`hibernate.cfg.xml`。这个文件是Hibernate应用的核心,它定义了数据源、...
这些配置项是 Hibernate 建立对象关系映射所需的基本信息。 Hibernate 配置文件是其核心组件之一,用于定义 Hibernate 的运行期参数。通过 hibernate.cfg.xml 和 .hbm.xml 文件,我们可以定义 Hibernate 的基本连接...
Hibernate则是一个强大的ORM(对象关系映射)工具,简化了数据库操作。 1. **Memcached的介绍**:Memcached是一个开源的、基于内存的键值存储系统,用于临时存储数据,减轻数据库负载。它通过TCP协议提供服务,支持...
**Hibernate基本配置演示** 在Java开发中,Hibernate是一款强大的对象关系映射(ORM)框架,它简化了数据库操作,使得开发者能够用Java对象来处理数据。这篇教程将深入讲解Hibernate的基本配置过程,确保你能顺利...
标题“Hibernate映射关系配置:XML方式和注解方式”涉及到的是Java持久层框架Hibernate中的一个重要概念——对象关系映射(ORM)。在这个主题中,我们将探讨如何通过XML映射文件和注解来配置Hibernate实体之间的关系...
在Java世界中,Hibernate是一个非常流行的对象关系映射(ORM)框架,它允许开发者将数据库操作转换为对Java对象的操作,极大地简化了数据库编程。在本实例中,我们将深入探讨如何使用XML配置文件来实现Hibernate的表...
Hibernate是Java领域中一款广泛应用的关系对象映射框架,它允许开发者将数据库操作抽象化,以对象的方式进行处理,极大地简化了数据库编程。`hibernate.properties`是Hibernate的核心配置文件,用于设定与数据库连接...
在Java开发领域,Hibernate作为一款强大的对象关系映射(ORM)框架,极大地简化了数据库操作。当与Oracle这样的大型数据库系统结合使用时,正确的配置是确保程序正常运行的关键。本文将深入探讨Hibernate与Oracle...
### Hibernate关系配置 Hibernate的配置是通过XML映射文件来完成的。内容中提到了如何在XML中配置实体类的基本映射: - **id映射**:通过`<id>`标签定义实体类的唯一标识符属性,可以配置生成策略。 - **property...
### Hibernate关联关系配置详解 #### 一、一对多与多对一关系配置 在软件开发过程中,实体之间的关联关系是常见的需求之一。其中,“一对多”与“多对一”的关系尤为常见,这类关系通常用来表示实体之间的层级或...
本知识点主要聚焦于Hibernate的配置文件,它是使用Hibernate进行数据库交互的基础。 首先,我们需要理解Hibernate的核心配置文件——`hibernate.cfg.xml`。这个文件是Hibernate与数据库建立连接的桥梁,它包含了...
《Hibernate之配置使用案例hibernate001》 Hibernate,作为Java领域中的一款主流对象关系映射(ORM)框架,极大地简化了数据库操作。它允许开发者使用面向对象的方式来处理数据库,而无需直接编写SQL语句。在本案例...
在Java开发中,Hibernate是一个非常重要的对象关系映射(ORM)框架,它简化了数据库操作,使得开发者可以使用面向对象的方式来处理数据。本教程将详细讲解如何在Java项目中配置和使用Hibernate,包括在普通Java工程...
hibernate配置文件 里面提供了连接数据库 数据库使用的方言 是否打印SQL语句 sql语句的格式 以及对象-关系映射文件的地址等
3. **配置Hibernate**:在主配置文件(如`applicationContext.xml`)中,配置Hibernate的数据源、SessionFactory和事务管理器。使用`LocalSessionFactoryBean`来创建SessionFactory,并设置数据库连接信息。同时,...
在Java世界中,Hibernate是一个非常重要的对象关系映射(ORM)框架,它简化了数据库操作,使得开发者可以更专注于业务逻辑而不是底层的数据访问细节。本文将深入探讨Hibernate配置文件,这是使用Hibernate进行数据库...
Hibernate_关联关系映射配置详解,希望能帮助广大java爱好者
### MyEclipse中Hibernate的基本配置步骤与理解 #### 一、引言 随着软件开发行业的不断发展,ORM(Object-Relational Mapping,对象关系映射)技术已成为连接对象模型与关系型数据库的重要桥梁之一。其中,...