Hibernate 关联映射之---- 一对多双向映射

简单的一对多,多对一单向映射可以就是双向映射的删减版。

以 班级(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的输出:



多了两条update,其实也很好理解,先插入学生信息,这个时候,还没有班级!所以只能先插入一个null的cid,等插入班级后,获取到cid,在update一次。
这就浪费了时间,需要注意点。

综上两点,得出一个结论,在一对多映射关系中,我们选择用多的一方维护一的一方,在一的一方的one-to-many标签中加入inverse=true;并且在添加时优先save一的一方,再save多的一方。
六 总结
其实含有例如cascade属性,以及与inverse的比较,都没说,可能会在其他关联映射中提到。
hibernate做的封装很有借鉴意义。
写学会用法,再理解原理,我认为两者都很重要。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值