一、引文:
Hibernate是在数据持久层的以面向对象的思想,解决数据操作问题,通过Hibernate我们只需要关注数据的对象模型而不用将太多的心思和时间花费在数据库设计中。简言之,Hibernate是一个轻量级PoJo框架,实体类的映射是这个框架的一个基础骨架。
二、实体类建立规则:(Persistent Classes)
在建立实体类的时候需要遵循一些规则:
1.默认的无参构造函数
HIbernate的Constructor.newInstance()函数调用
2.提供一个标示属性(可选)
如果某些实体类没有标识属性,Hibernate的级联操作、Session.saveOrUpdate()、Session.merge()这些功能是无法使用的。在继承映射中,仍然可以使用这些方法。
3.不使用finall关键字修饰
4.为需要持久化的字段提供get、set方法
如果是uuid方式生成主键,那么只需要提供getId()方法。
三、实体类到数据库表的映射:
1、实体类到数据库表的映射:——hibernate.cfg.xml
表级别的映射,要放到hibernate.cfg.xml文件中,语法比较固定,在类中包的路径层次使用点号表示,在这里使用"/"表示路径符,指引实体类映射文件的路径,如:
<mapping resource="com/bjpowernode/hibernate/Student.hbm.xml"/>
<mapping resource="com/bjpowernode/hibernate/Classes.hbm.xml"/>
在hibernate.cfg.xml映射文件中,还可以配置sql语句的一些现实信息:是否在控制台显示sql语句,特定sql语句的执行方式(hbm2ddl):
<!--控制台显示sql语句-->
<property name="hibernate.show_sql">true</property>
<!--格式化显示sql语句-->
<property name="hibernate.format_sql">true</property>
hbm2ddl:
<property name="hibernate.hbm2ddl.auto">update</property>
设置批量系统抓取和更新数据的策略,对性能调优很关键:
<!-- 批量更新配置 -->
<property name="hibernate.jdbc.batch_size">30</property>
<!-- 批量抓取参数的配置 -->
<property name="hibernate.jdbc.fetch_size">50</property>
数据库的连接配置:
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/hibernate_fetch</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">XXXXXX</property>
<!--配置使用的是MySQL数据库,允许MySQL相关的特有功能-->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
缓存配置:
<!--配置使用缓存机制-->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!--开启二级缓存-->
<property name="hibernate.cache.use_second_level_cache">true</property>
在hibernate.cfg.xml文件中还可以进行其他的很多配置,诸如:事务、日志等,这里也是“面向切面”的一个具体体现。
2、属性字段的映射:主键和普通属性和实体类之间以及集合属性的映射。
主键:
单纯的主键映射分为三种:uuid、native、assigned、与具体数据库相关的sequence、incrument等,不推荐使用,因为移植性不好。
uuid:实体属性中id字段属性设置为String,只提供get方法。
<id name="id" column="user_id" length="32" access="field">
<generator class="uuid"/>
</id>
native、assigned映射:实体类中数据类型为int,并提供get和set方法
<id name="id">
<generator class="native"/>
</id>
普通属性:使用property标签
<property name="name" column="user_name" length="30" unique="true" not-null="true"/>
除了name其他属性都是可选的。
Component映射:
代码复用的一种机制,如图:
Contact类不是实体类,它没有主键,它将作为其他类的一部分参与映射。
<component name="userContact">
<property name="email"/>
<property name="address"/>
<property name="zipCode"/>
<property name="contactTel"/>
</component>
关联字段的映射:
实体与实体之间是有关联关系的,对应数据库表之间的一对一、多对一、一对多、多对多等对应关系,Hibernate有一套与之相匹配的对应规则,通过这些规则,可以达到自动建表的功能。
1.一对一:
一对一同一主键(单向):两个表使用的是同一个主键。
在Person中有 private IdCard idCard;声明。
<class name="com.bjpowernode.hibernate.Person" table="t_person">
<id name="id">
<!-- 采用foreign生成策略,forgeign会取得关联对象的标识 -->
<generator class="foreign">
<!-- property只关联对象 -->
<param name="property">idCard</param>
</generator>
</id>
<property name="name"/>
<!--
one-to-one指示hibernate如何加载其关联对象,默认根据主键加载
也就是拿到关系字段值,根据对端的主键来加载关联对象
constrained="true表示,当前主键(person的主键)还是一个外键
参照了对端的主键(IdCard的主键),也就是会生成外键约束语句
-->
<one-to-one name="idCard" constrained="true"/>
</class>
一对一的同一主键的双向关联:
在Card类中添加对Person的引用,在配置文件中添加相关映射:
<one-to-one name="person"/>
一对一外键关联:常见的主外键的关联,期实质是多对一的一种特例,在关联的一方只需要限制唯一性。
Person类对Card的外键关联:在Person中有此字段声明private IdCard idCard,并在配置文件中添加关联映射:
<many-to-one name="idCard" unique="true"/>
双向一对一外键关联:
Card中建立对Person的引用,并在配置文件中添加配置:
<one-to-one name="person" property-ref="idCard"/>
其他字段之间的关联映射:
如果一个字段是另一个实体类的引用,如果是多对一那么在多的一方添加外键关联字段。
2.多对一:多的一端有对一端的引用,并添加下面的配置:
<many-to-one name="group" column="groupid" cascade="save-update"/>
3.一对多:
在一(Classes)的那端建立对多(Student)的实体类的引用,在一的那端使用set配置映射:
<set name="students">
<key column="classesid"/>
<one-to-many class="com.bjpowernode.hibernate.Student"/>
</set>
将一对多的控制交给多的一端:
在多(Student)的一端添加对一(Classes)的引用,并添加如下配置:
<many-to-one name="classes" column="classesid"/>
这里的column需要和一对多端的set中的key一致。
同时使用inverse属性修饰set标签:
<set name="students" inverse="true">
另外,为了减少sql语句,可以给set标签添加lazy属性,三个取值true/false/extra默认是延迟加载,extra支持在hql中使用count(*)这样的语句,减少sql语句数,提高性能。
如果是多对多,需要引入,第三张表映射,需要注意字段之间的对应关系。
4.单向多对多:
在User类中建立对Role类的引用,并在配置文件中建立映射:
<set name="roles" table="t_user_role">
<key column="user_id"/>
<many-to-many class="com.bjpowernode.hibernate.Role" column="role_id" />
</set>
5.双向多对多:
在Role类中再建立对User的引用,并采用类似的做法,添加映射:
<set name="users" table="t_user_role">
<key column="role_id" not-null="true"/>
<many-to-many class="com.bjpowernode.hibernate.User" column="user_id"/>
</set>
关于关联实体字段映射的一点总结:
一对多和多对一之间通过一个集合,多对多通过第三张表,两个映射文件中表名和对应 字段都要一致。不管是一对多还是多对一,通常情况下都会将关系放到多的一方来维护(inverse)。 这样可以减少sql语句,提高性能。因为在一的一端添加数据的时候,多的一端的关联字段都是空值,等后续填充数据的时候,每行的数据都会被重新跟新一次。
3、集合映射:
常见的集合:set、List 、数组、Map,set是无序的,而其他的集合在映射的时候,扔需要保存数据的存储顺序。
实体类中这些集合的定义:
对应的映射:private Set setValues = new HashSet(); private List listValues = new ArrayList(); private String[] arrayValues; private Map mapValues;
<set name="setValues" table="t_set_values"> <key column="set_id"/> <element type="string" column="set_value" not-null="true"/> </set> <list name="listValues" table="t_list_values"> <key column="list_id"/> <list-index column="list_index"/> <element type="string" column="list_value"/> </list> <array name="arrayValues" table="t_array_values"> <key column="array_id"/> <list-index column="array_index"/> <element type="string" column="array_value"/> </array> <map name="mapValues" table="t_map_values"> <key column="map_id"/> <map-key type="string" column="map_key"/> <element type="string" column="map_value"/> </map>
每种集合都有类似的标签与之对应,集合的映射都映射成了一个表,都需要key这个标签来指明检索,使用element表示元素,另外,list和数组都使用了list-index指明顺序,map使用的是map-key。
4、继承映射:
可以映射成三种情况:一棵树一张表、每个类一张表、每个具体类一张表。
一棵树一张表:
使用鉴别字段(discriminator),子类被映射在subclass标签中,最终生成一张表:
<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>
每个类一张表:
子类被映射在joined-subclass标签中:
<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>
会在子类对应的表中生成
key标签中的列名字段。
每个具体类一张表:
子类映射在union-subclass标签中:
<union-subclass name="Pig" table="t_pig">
<property name="weight"/>
</union-subclass>
<union-subclass name="Bird" table="t_bird">
<property name="height"/>
</union-subclass>
每个子类表都含有父类的字段,这里不再需要key标签。
三种映射的对比:
1.子类可以放到父类的class标签中,也可以放到与之平行的标签中,但是需要使用extend属性修饰。
2.一棵树一张表,使用discriminator鉴别字段,Hibernate会根据鉴别字段自动存储或加载关联对象。每个类使用joined-subclass,并且子类需要key标签来指明对父类的引用字段,每个具体类,使用union-subclass,因为会在子类中包含全部字段,所以,就无需使用key了。
3.每个具体类一张表中,主键不能冲突,所以,主键不能使用数据库自增的方式。
4.在每个具体一张表中类映射中,使用abstract修改父类,不会生成表
<class name="Animal" table="t_animal" abstract="true">
5.get和hql以及lazy为false的load支持多态查询(可以使用instancof)鉴别类型。因为load默认支持lazy,所以我们看到的是l的代理。
Animal a = (Animal)session.load(Animal.class, 1);
//因为load默认支持lazy,所以我们看到的是Animal的代理
//所以采用instanceof无法鉴别出真正的类型Pig
//所load在此情况下是不支持多态查询的
//多态查询:hibernate在加载数据的时候,能够采用instancof鉴别出其真正的类型
if (a instanceof Pig) {
System.out.println(a.getName());
}else {
System.out.println("不是猪!");
}
四、最后总结一下一对多映射在树形结构中的应用:
一对多双向关联 在表的内部建立父子关系 可以无限递归,是一个树形结构:Area类中建立自身的引用(表的自关联):映射文件:private Area parent; private Set children;
<class name="com.bjpowernode.hibernate.Area" table="t_area"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <many-to-one name="parent" column="pid"/> <set name="children" lazy="extra" inverse="true"> <key column="pid"/> <one-to-many class="com.bjpowernode.hibernate.Area"/> </set> </class>
值得注意的是:
1.不是所有的字段都会映射成数据库的字段(使用@Transient修饰)。
2.不是所有的实体类都会映射成表(继承映射中的每个具体类一张表)。