简单的一对多,多对一单向映射可以就是双向映射的删减版。
以 班级(1)对学生(多)为例
一. JavaBean设计
注意:
对应的getter setter别忘了(图示省略)。这里的注意点有一个,班级类中设置一个set对应学生集合,需要初始化,否则直接插入会报NullPointerException。当然,如果你在类中初始化,在代码中初始化也是可以的,但是强调的是,set,list,map等类属性一定要new!普通类(非集合)属性其他的就别画蛇添足写new了,否则也会出现错误。
二,DB设计
先来考虑下,如果我们自己设计数据库,该怎么维护这个一对多双向映射的关系呢?肯定得存储两张表,姑且称之为c_clazz班级表,c_student学生表,然后为了建立关联,学生表设置一个班级编号cid,对应了班级表的主键cid,这样学生就对应上班级了。
画图理解,大概如下:
注意:
javabean中的对象是无法直接在表中通过字段反应的。所以,初学者不用考虑为什么数据库中没有一个字段名叫:students,clazz啊?而是该这么理解,一张表对应一个类,而映射关系是通过主外键关联,另开一个表等等方法实现的(例如多对多关系)。而数据库设计肯定也不止我上述这一种可能,但是hibernate选择的就是如上的方式。
三. 配置Student.hbm.xml ,Clazz.hbm.xml类配置文件
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="edu.mapping.one2many">
<class name="Clazz" table="c_clazz">
<!-- 主键 ,映射-->
<id name="cid">
<generator class="native"/>
</id>
<!-- 非主键,映射 -->
<property name="cname"></property>
<set name="students" inverse="true" >
<!-- 使用<key>指定引用至自身的外键表中的外键 -->
<key column="cid"></key>
<one-to-many class="Student"/>
</set>
</class>
</hibernate-mapping>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="edu.mapping.one2many">
<class name="Student" table="c_student">
<!-- 主键 ,映射-->
<id name="sid">
<generator class="native"/>
</id>
<!-- 非主键,映射 -->
<property name="sno"></property>
<property name="sname"></property>
<many-to-one name="clazz" column="cid" class="Clazz" ></many-to-one>
</class>
</hibernate-mapping>
注意:
1.many-to-one,one-to-many
Clazz.hbm.xml中的学生集合属性是通过一个set标签来描述的,外键关系的声明也是在这里(即在一的这一方设置),需要注意<key column="cid"></key>与Student.hbm.xml中的<many-to-one name="clazz" column="cid" class="Clazz" ></many-to-one>两者的列名必须一致。在Clazz.hbm.xml中声明
<key column="cid"></key>
<one-to-many class="Student"/>
是在告诉hibernate:"我外面有个表(类Student对应的表)有一个叫做cid的列是我的外键".但是具体的这个列对应是Student类中哪个属性的呢?当然需要在Student.hbm.xml中说明了
<many-to-one name="clazz" column="cid" class="Clazz" ></many-to-one>
是在告诉hibernate:“我外面有个表(类Clazz对应的表)我的cid列(也就是类里面的clazz属性)需要参考他的主键”。
2.配置原则
因为这是我的第一篇关于hibernate配置的博客,所以我讲下我学习配置过程中的一个心得,就是多试,观察数据库中的区别。其中有一点,有多属性可选,有很多属性必写,怎么区分呢?有一个原则就是,理解通过这个配置能否找到column对应的name,能不能找到一个类?然后再另一个类中能不能通过name找到column,就是通过传递性,判断是否建立起正确的关联。
举个例子:
Clazz.hbm.xml:
<set name="students" inverse="true" ><!-- <set name="students" inverse="true" table="c_student" > -->
<key column="cid"></key>
<one-to-many class="Student"/>
</set>
也可以像注释里面那样写,加上表名。但是为什么可以不写呢?我们来看传递性,Clazz.hbm.xml通过class="Student"的声明,已经说明了,我关联的是Student类对应的那个表。而在Student.hbm.xml中已经有了这么一行:
<class name="Student" table="c_student">
所以等于说,通过传递性,找到了对应的表。其他的一些类似的属性也可以这么理解。
3.
再来讲解某个特殊的属性
inverse="true"
就是说,hibernate默认,一个类关联另一类之后,我可以操作另外一个表,就是说班级可以添加学生,但是加上这一句:控制反转=真,就是说,不给你添加了。
有什么意义,先说结论:强制你提高效率。等下面代码部分做具体比较。
四. hibernate总配置文件hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<!-- 通常,一个session-factory节点代表一个数据库 -->
<session-factory>
<!-- 1. 数据库连接配置 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hib_demo2</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<!--
数据库方法配置, hibernate在运行的时候,会根据不同的方言生成符合当前数据库语法的sql
-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 2. 其他相关配置 -->
<!-- 2.1 显示hibernate在运行时候执行的sql语句 -->
<property name="hibernate.show_sql">true</property>
<!-- 2.2 格式化sql
<property name="hibernate.format_sql">true</property>
-->
<!-- 2.3 自动建表 -->
<property name="hibernate.hbm2ddl.auto">create</property>
<!-- 3. 加载所有映射
<mapping resource="cn/itcast/a_hello/Employee.hbm.xml"/>
-->
</session-factory>
</hibernate-configuration>
注意:
自动建表,这里用的create,会自动删除表的!如果已经建立数据了,还是改成update吧。
加载映射,我们是测试阶段,在下面的App.class中动态添加
五. 编写测试代码App.class
package edu.mapping.one2many;
import java.util.List;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
import org.junit.Test;
public class App {
private static SessionFactory sf =null;
static {
sf = new Configuration()
.configure()
.addClass(Clazz.class)
.addClass(Student.class)
.buildSessionFactory();
}
@Test
public void list(){
Session session = sf.openSession();
session.beginTransaction();
Query query = session.createQuery("from Clazz ");
List<Clazz> clazz = query.list();
System.out.println(clazz);
session.getTransaction().commit();
session.close();
}
@Test
public void insert(){
Session session = sf.openSession();
session.beginTransaction();
Clazz clazz = new Clazz();
clazz.setCname("计算机131");
Student st1 = new Student();
Student st2 = new Student();
st1.setSname("徐璇");
st1.setSno(13416124);
st2.setSname("徐波");
st2.setSno(13416122);
st1.setClazz(clazz);
st2.setClazz(clazz);
session.save(clazz);
session.save(st1);
session.save(st2);
session.getTransaction().commit();
session.close();
}
}
看看console的输出:
这说明了什么?我们来仔细分析下。
1.看看hibernate帮我们执行的语句:先插入了班级,后插入的两天学生。但是学生的cid却并没有对应到班级的cid,这里就关系到我们上述配置中inverse这个属性了,还记得之前我们的结论是什么吗?一的一方不能操作多的一方
clazz.getStudents().add(st1);
clazz.getStudents().add(st2);
我们违反了这个规定,但是他不会报错,而是会不给你设置外键关联。
解决方法:要么去除inverse或者inverse=false(默认);要么通过学生操作班级。
//clazz.getStudents().add(st1);
//clazz.getStudents().add(st2);
st1.setClazz(clazz);
st2.setClazz(clazz);
在运行一次,看看数据库:
成功了。
2. 我们试试把
session.save(clazz);
session.save(st1);
session.save(st2);
调换一下顺序,看看有什么变化
变成:
session.save(st1);
session.save(st2);
session.save(clazz);
看看console的输出:
这就浪费了时间,需要注意点。
综上两点,得出一个结论,在一对多映射关系中,我们选择用多的一方维护一的一方,在一的一方的one-to-many标签中加入inverse=true;并且在添加时优先save一的一方,再save多的一方。
六 总结
其实含有例如cascade属性,以及与inverse的比较,都没说,可能会在其他关联映射中提到。hibernate做的封装很有借鉴意义。
写学会用法,再理解原理,我认为两者都很重要。