hibernate多表操作
多对一
创建学生和班级的类以及对应的映射文件,多对一的情况多个学生属于一个班级,以此为例。类中除了基本的属性需要在学生类中添加一个班级的引用。
班级类
package com.hibernate.entity;
public class Grade implements java.io.Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String gradeName;
学生类
package com.hibernate.entity;
public class Student implements java.io.Serializable{
private static final long serialVersionUID = 1L;
private String id;
private String stuName;
private Grade grade;
}
在映射文件中普通的属性已经通过普通的标签进行了映射,对于引用的属性,需要运用many-to-one标签进行映射。注意该标签要在多的那一方类的映射文件中添加。
name属性:引用的名称;
class属性:对应的类可以指明也可以不指明;
column属性:外键名称自己随便起;
添加完该标签之后将映射文件添加到连接文件中。
<?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>
<class name="com.hibernate.entity.Student" >
<id name="id">
<generator class="uuid" />
</id>
<property name="stuName" />
<many-to-one name="grade" class="com.hibernate.entity.Grade" column="fk_s_g"/>
</class>
</hibernate-mapping>
测试类
public static void main(String[] args) {
Configuration cfg = new Configuration().configure();
SchemaExport export = new SchemaExport(cfg);
export.create(true, true);
}
运行测试类结果中可以看到有生成两个表的语句和外键的语句。
观察数据库中生成的表,可以在学生表中看到外键已经被创建。
测试添加
将多个学生保存到一个班级中,创建一个班级临时对象,将班级类持久化,创建多个学生持久对象。
public static void saveManyToOne() {
Grade grade = new Grade();
grade.setGradeName("测试班级1");
BaseDao.saveOrUpdate(grade);
Student student1 = new Student();
student1.setStuName("娃娃");
student1.setGrade(grade);
BaseDao.saveOrUpdate(student1);
Student student2 = new Student();
student2.setStuName("张三");
student2.setGrade(grade);
BaseDao.saveOrUpdate(student2);
}
从运行结果的sql语句中可以看到外键已经insert进去。
可以看到已经将班级表的id拿来存到学生表中,是hibernate自己存储的。
测试查询
查学生类就可以了。当输出的信息包括班级是就会报错。
报错原因:配置文件中少配置一个属性。lazy:值为false,将其添加即可成功运行。属性设置为false,让延迟加载,其实本质是不让hibernate使用JDK的动态代理机制。
lazy=”false”指定此关联总是被预先抓取
<many-to-one name="grade" class="com.hibernate.entity.Grade" column="fk_s_g" lazy="false"/>
通过设置断点进行调试,在有该标签时,可以看到班级类的内容已经被获取到。
没有时添加该标签时,班级类的内容没有被获取。主要是因为他的底层用的是JDK的动态代理,当你不用的时候不查出来,用的时候才查出来。添加这个标签是屏蔽了动态代理。动态代理在查询数据时不是从数据库中查,从内容中查。就好比买火车票在代理商处买,而不是直接去火车站买更加方便。动态代理在此可以理解为不加载引用类中的属性。
从运行结果可以看出查询是先查从表(学生表)再查主表(班级表)。
一对多
一对多情况:一个班级包括多个学生。以此为例,完成一对多的操作,需要在班级类中装多个学生,在其中创建一个set集合(选择set因为选其他集合有下标最终会在数据库中生成表的字段),其中存储student,并声明该集合的get,set方法。删除掉在多对一时对学生类的操作。
private Set<Student> students=new HashSet<Student>();
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
如何在映射文件中对集合来进行映射:运用set标签,name属性:是属性的名称;key标签是外键,其中的column属性值是外键名;关系用one-to-many标签,其中class属性是集合中存储内容对应的类名称。
<set name="students">
<key column="fk_s_g" />
<one-to-many class="com.hibernate.entity.Student" />
</set>
运行后查看生成的数据库发现与之前多对一生成的表是一致的,在学生表中生成了一个外键,说明两种情况的主外键关系是一致的,只是在类的层面发生变化。
测试添加
创建一个班级对象,通过集合对应的get方法得到班级里的学生集合,在往集合中添加学生,之后在将集合设置到班级里,在学生类里创建一个构造函数(通过姓名创建对象),将班级类持久化,学生是临时对象。
public static void saveOneToMany() {
Grade grade=new Grade();
grade.setGradeName("测试一对多");
Set<Student> students=grade.getStudents();
students.add(new Student("张三"));
students.add(new Student("李斯"));
students.add(new Student("王五"));
grade.setStudents(students);
BaseDao.saveOrUpdate(grade);
}
在主函数中运行此方法,因为是一个持久对象可以调用临时对象,会报如下错。
解决办法在set标签中添加cascade属性:级联操作。指明哪些操作会从父对象级联到关联的对象。
<set name="students" cascade="all">
再次运行,通过sql语句可以看到此时外键是通过更新完成的。
测试查询
查询比较麻烦因此一般将一对多转换为多对一,调用dao层方法查找班级,得到班级里面的学生集合,设置迭代器输出学生姓名,注意要在set标签中添加lazy属性并将值设置为false。
public static void findOneToMany(){
Grade grade=(Grade) BaseDao.getUser("from Grade where id='402881e55de38bcb015de38bd0e70001'");
System.out.println(grade.getGradeName());
Set<Student> students=grade.getStudents();
Iterator<Student> iter=students.iterator();
while(iter.hasNext()){
System.out.println(iter.next().getStuName());
}
}
在一个类中添加是单向,当然也可以在两个类中都添加是双向,但这样做会造成一些不合理的问题,例如:转成json格式是会造成死循环。