上一节我们主要研究下了Hibernate中的一一映射和多对一映射,这节我们看下Hibernate中的其他几种映射,包括一对多映射,多对多映射,复合主键映射及继承映射。
第一种是一对多映射,“一对多”,顾名思义,是由“一”的一端加载“多”的一端,关系交由“一”来维护。反映在Java代码中就是在“一”的一端中持有“多”一端的集合,而hibernate把这种关系反映到数据库的策略是在“多”一端的表上加上一个外键指向“一”一端表。显现,其实这采用的还是“多対一”的映射原理。 但是,在“一”一端维护关系是我们不提倡的,因为它有不可避免的缺点,即级联插入数据的时候要先插“多”一端,这样造成了两方面的不妥:1.如果我们把“多”一端的外键必须添加非空约束,将导致数据不能插入;2.即使外键不设置为非空,在插入“多”一端数据时外键将暂时为空( 因为此时它所引用的“一”记录还没有插入),而只有等到它所引用的“一”记录插入后,再发出update语句修改外键,这样的效率必然降低。这里我们用class和student举例。
<class name="com.xiaoming.test.hibernate.classTest.Classes" table="classes2"> <id name="id" length="4"> <generator class="native"></generator> </id> <property name="name" length="10"></property> <set name="students" cascade="save-update"> <key column="class_id"></key> <one-to-many class="com.xiaoming.test.hibernate.classTest.Student"/> </set> </class>
key的含义,指在另一端增加的外键指向本主键.如果设置上属性not-null="true",表示该外键非空,则在由"一"的一端维护关系时,可能导致插入数据异常PropertyValueException.
one-to-many含义,指出set集合中的元素类型,以供加载时使用
set标签后面还可以加上inverse=ture属性,inverse="true"含义,把关联关系交由对方一端维护,而在操作本方数据时不再维护关系
<class name="com.xiaoming.test.hibernate.classTest.Student" table="student2"> <id name="id" length="4"> <generator class="native"></generator> </id> <property name="name" length="10"></property> <many-to-one name="classes" column="class_id" cascade="save-update"></many-to-one> </class>
测试方法如下:
Student student1=new Student(); student1.setName("奇隆"); Student student2=new Student(); student2.setName("有朋"); Set<Student> students=new HashSet<Student>(); students.add(student1); students.add(student2); Classes classes=new Classes(); classes.setName("不一班"); classes.setStudents(students); session.save(classes);
这种情况下如果没有配置cascade属性的话,存储class不成功,因为class对象引用了student对象student1和student2。
Student student1=new Student(); student1.setName("奇隆"); session.save(student1); Student student2=new Student(); student2.setName("有朋"); session.save(student2); Set<Student> students=new HashSet<Student>(); students.add(student1); students.add(student2); Classes classes=new Classes(); classes.setName("不一班"); classes.setStudents(students); session.save(classes);
执行ok,有两种情况,一种是<set>标签中没有inverse="true。会先执行3条insert语句,分别插入student1和student2对象及class对象,但是此时student对象的classid为空,所以在执行完insert语句后还会执行对应的update语句来更新对应的student中对应的classid,对应语句如下:
Hibernate: insert into calss(name, id) values (?, ?)
Hibernate: insert into student(name, id) values (?, ?)
Hibernate: insert into student (name, id) values (?, ?)
Hibernate: update student set class_id=? where id=?
Hibernate: update sstudent set class_id=? where id=?
如果set标签中有inverse=true的时候,表示把关系交给对方来维护,自己不负责维护相应的操作。存储过程是这样的:存classes时,由于有cascade="save-update",它会先触发存储student; 而在存储student时,inverse="true"指明了由它来维护关联关系,所以他要先存主表class,再回来存副表student
所以对应的sql如下:
Hibernate: insert into sxt_hibernate_class (name, id) values (?, ?)
Hibernate: insert into sxt_hibernate_student (name, class_id, id) values (?, ?, ?)
Hibernate: insert into sxt_hibernate_student (name, class_id, id) values (?, ?, ?)
相应的读取代码如下:
Classes classes = (Classes) session.load(Classes.class, 3); System.out.println(classes); Set<Student> students = classes.getStudents(); for (Iterator<Student> stus = students.iterator(); stus.hasNext();) { System.out.println(stus.next()); }
接下来我们要研究的是多对多的映射,多对多常常用中间表来解决相互的关系,这样我们就要使用中间表或中间类来解决。中间类就是把我们的中间表抽象生成一个实体类,在映射的时候分别和两个关联类构成一对多的关系,即演变成两个一对多来处理。这里我们用role和player的对象来说明一下多对多的使用.
<class name="com.xiaoming.test.hibernate.roleTest.Player" table="t_player"> <id name="id" length="4"> <generator class="native"></generator> </id> <property name="name" length="10"></property> <set name="roles" table="t_player_role" cascade="save-update"> <key column="playerid"></key> <many-to-many class="com.xiaoming.test.hibernate.roleTest.Role" column="roleid"></many-to-many> </set> </class>
table属性的含义,用来指定中间表 ,set中的key column属性含义,指定中间表中用来指向本表的外键
<class name="com.xiaoming.test.hibernate.roleTest.Role" table="t_role"> <id name="id" length="4"> <generator class="native"></generator> </id> <property name="name" length="10"></property> <set name="players" table="t_player_role" cascade="save-update"> <key column="roleid"></key> <many-to-many class="com.xiaoming.test.hibernate.roleTest.Player" column="playerid"></many-to-many> </set> </class>
测试代码如下:
Role role1=new Role(); role1.setName("后卫"); Role role2=new Role(); role2.setName("前锋"); Role role3=new Role(); role3.setName("中锋"); Player player1=new Player(); player1.setName("姚明"); Set<Role> roles1=new HashSet<Role>(); roles1.add(role3); player1.setRoles(roles1); Player player2=new Player(); player2.setName("詹姆斯"); Set<Role> roles2=new HashSet<Role>(); roles2.add(role1); roles2.add(role2); roles2.add(role3); player2.setRoles(roles2); session.save(player1); session.save(player2);*/
这个代码可以正确执行,每次保存play的时候都能级联保存对应的role和相关的关系表数据。
另一种使用方法如下:
Player player1=new Player(); player1.setName("姚明"); Player player2=new Player(); player2.setName("詹姆斯"); Player player3=new Player(); player3.setName("科比"); Role role1=new Role(); role1.setName("中锋"); Set<Player> players1=new HashSet<Player>(); players1.add(player1); players1.add(player2); role1.setPlayers(players1); Role role2=new Role(); role2.setName("后卫"); Set<Player> players2=new HashSet<Player>(); players2.add(player2); players2.add(player3); role2.setPlayers(players2); session.save(role1); session.save(role2);*/
是通过role的维度来插入对应的player。
加载代码如下:
Player player=(Player)session.load(Player.class, 1); System.out.println(player); for(Iterator<Role> iterator=player.getRoles().iterator();iterator.hasNext();){ System.out.println(iterator.next()); }
接下来是符合主键的映射,使用了composite-id标签,对应到一个符合主键类。这里我们用department来说明符合主键使用,department的area和name为其复合主键,对应的配置如下:
<class name="com.xiaoming.test.hibernate.departmentTest.Department" table="department"> <!-- 联合主键 --> <composite-id name="departmentPK"> <key-property name="area" /> <key-property name="name" /> </composite-id> <property name="empCount" length="4" /> <property name="birthday" type="date" /> </class>
其中departmentPk是复合主键类,对应的测试代码如下:
//插入数据 Department dept = new Department(); /** 生成主键对象 */ DepartmentPK deptPK = new DepartmentPK(); deptPK.setArea("area1"); deptPK.setName("dept1"); dept.setDepartmentPK(deptPK); dept.setEmpCount(100); dept.setBirthday(new Date()); session.save(dept); //读取数据 DepartmentPK deptPK = new DepartmentPK(); deptPK.setArea("area1"); deptPK.setName("dept1"); Department dept=(Department)session.load(Department.class, deptPK); System.out.println(dept.getDepartmentPK().getArea()+","+dept.getEmpCount()); //更新数据 DepartmentPK deptPK = new DepartmentPK(); deptPK.setArea("area1"); deptPK.setName("dept1"); Department emp=(Department)session.load(Department.class, deptPK); System.out.println(emp.getDepartmentPK().getArea()+","+emp.getDepartmentPK().getName()+","+emp.getEmpCount()+","+emp.getBirthday()); emp.setEmpCount(100); session.saveOrUpdate(emp); DepartmentPK deptPK2 = new DepartmentPK(); deptPK2.setArea("area1"); deptPK2.setName("dept1"); Department dept=(Department)session.load(Department.class, deptPK2); System.out.println(dept.getDepartmentPK().getArea()+","+dept.getDepartmentPK().getName()+","+dept.getEmpCount()+","+dept.getBirthday());
在映射关系中我们经常用到的两个属性有invere和cascade。cascade定义的是源头的对象插入或删除对象时,级联的关系对象也会做相关的操作。inverse属性对于多对多中带有关系表的时候会讲对关系表的操作放到另一方去操作。简单的说cascade是一种级联的程度,而invers表示的是是否维持两个实体的关系(外键)
在Hibernte中还有一种映射是继承映射,继承也分3种,
1 单表继承:每颗类继承树使用一个表(table per class hlerarchy)
2 具体表继承:每个子类一个表(table per subclass)
3 类表继承:每个具体类一个表(table per concrete class)
假设我们有基本类animal属性有id,name和sex,其子类bird,有属性height,子类pig有属性weight
对于第一种情况,多个类信息存储在一张表中,可能由表中的一个字段来标注具体是哪个子类,相应的配置如下:
<class name="Animal" table="t_animal" lazy="false"> <id name="id"> <generator class="native"/> </id> <discriminator column="type" type="string"/> <property name="name"/> <property name="sex"/> <subclass name="Pig" discriminator-value="P"> <property name="weight"/> </subclass> <subclass name="Bird" discriminator-value="B"> <property name="height"/> </subclass> </class>
其中,父类使用discriminator来指定区分不同子类的字段,子类使用discriminator-value来表名本子类的discriminator字段。
对于第二种情况,这种策略是使用joined-subclass标签来定义子类的。父类、子类,每个类都对应一张数据库表。 在父类对应的数据库表中,实际上会存储所有的记录,包括父类和子类的记录;在子类对应的数据库表中, 这个表只定义了子类中所特有的属性映射的字段。子类与父类,通过相同的主键值来关联。其配置关系如下:
<class name="Animal" table="t_animal"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <property name="sex"/> <joined-subclass name="Pig" table="t_pig"> <key column="pid"/> <property name="weight"/> </joined-subclass> <joined-subclass name="Bird" table="t_bird"> <key column="bid"/> <property name="height"/> </joined-subclass> </class>
其中,join-subclass标签的name属性是子类的全路径名,key标签用来执行子类和父类是通过哪个字段关联的。
对于第3中情况,这种策略是使用union-subclass标签来定义子类的。每个子类对应一张表,而且这个表的信息是完备的, 即包含了所有从父类继承下来的属性映射的字段,相关的配置如下:
<class name="Animal" abstract="true"> <id name="id"> <generator class="assigned"/> </id> <property name="name"/> <property name="sex"/> <union-subclass name="Pig" table="t_pig"> <property name="weight"/> </union-subclass> <union-subclass name="Bird" table="t_bird"> <property name="height"/> </union-subclass> </class>
这里unionclass不需要再包含key属性了。另外class中的abstracet属性如果为true的话则不会生成表结构,如果为false会生成表结构,但是不会插入数据。
总结一下,这里主要介绍了多对一,多对多的级联关系,还有其他的如复合主键映射和继承映射的关系。下一节将学习的是Hibernate中最强大的Hql部分。