2010年5月10日开篇
前言:
为了身体力行“一段时间内集中精力做一件事”,今天起将原来的学习安排进行调整,晚上的时间集中的学习Hibernate的设计思路和应用方法。将每天的学习成果整理以后发布到网上,作为备忘,同时也可以接受网友们的监督,以防自己错误的理解了相关知识而不自知。
学习的教材方面,我选的《Manning.Java.Persistence.with.Hibernate.Nov.2006》,这本书的作者中有一个是Hibernate的设计者 ,由他讲解的一些Hibernate的设计思路应该是更有说服力。在此之前,我已经看过前三章了,因此以下的系列随笔就从第四章开始。
学习的节奏呢,尝试使用Pomodoro技术。
学习的原则,就是2/8法则,以点带面。
本章的重点内容有两个:
一是讲解基本的OR映射选项,这些选项如何影响Hibernate加载和保存数据的行为,并讲解这些选项在JPA Annotation、JPA XML Descriptors、Hibernate Native XML Format三种格式下的表示方式;
二是在细粒度的对象模型中,对象的属性和内部子对象如何映射到关系数据模型上。
好了,下面开始本章的学习整理。
一、
首先,先来澄清两个概念,实体Entity Type和值类型Value Type。
在我感觉,作者在这张开篇就引入这两个概念的区分,应该和DDD领域驱动设计理念中的EntityType和ValueType类似。
Entity Type是领域模型里面最重要的,有唯一身份标志的对象(比如Person),在ORM方案中这个唯一的标志被映射为关系数据库的主键;相对的,Value Type则是次要的,没有身份标志的对象,比如所有的JDK对象或者用户自定义Type。
Entity Type对象可被不同的其他实体引用,对其中一个引用修改会影响到其他引用,在ORM中这种引用关系被映射成关系数据库的外键;而Value Type的对象则被某个实体独占,对它的修改不会对其他实体造成任何影响,在ORM中这些对象被映射成关系数据库的Row的一部分。在建模时,我们可以依据实体是否有必要共享引用,来作出是否将一个对象建模为EntityType的决策。
Entity Type可以独立于其他对象而存在,有自己独立的生命周期;Value Type只有依附于其他对象,它的存在才有意义,生命周期由它所属的Entity Type控制,由实体来创建和销毁属于它的ValueType,例如User被删除,User对象的Address必须被同时删除。
Entity Type之间的关系多为聚合或者引用;Entity Type和Value Type之间的关系多为组合。
Best Practise
在UML建模时,使用Stereotype来区分Entity Type和Value Type,可以让你直观的区分出两种不同的对象,而这往往是设计well-performmng的persistence layerr的第一步。
先把所有对象建模成Value Type,然后根据是否需要共享,将其中某些对象提升为Entity Type。
尽量的简化对象之间的关系,过度的复杂性都设计毫无益处。
二、Hibernate对细粒度模型fine-grained domain model的支持
细粒度模型是一个丰富的领域模型最重要的特点,最直观的感觉模型被划分的很细致,相比粗粒度模型被建模出更多的class。
细粒度模型的优点就是具有更高内聚性,提高了代码的重用程度,并且比粗粒度的模型更符合人脑理解事物的模式,从而更容易理解。
过去的ORM产品往往无法为细粒度模型向关系模型的转换提供解决方案,而Hibernate将这一问题完美的解决了。Hibernate强调使用细粒度的模型来实现数据的类型的safety和behavior。例如,相比把一个User的email属性建立成String形式的,把email属性建模成一个EMail类型,意味着可以将EMail的格式校验集成到该类内部,甚至为其加入一个sendEMail()行为使其更加灵活。
细粒度模型中,根据需要将其中的某些对象建模为Entity Type。
2010年5月11日继续---因为今天请几个好朋友吃饭,所以今天的学习进度可能比昨天稍微少一些;另外,今天没咖啡喝了,好困…
三、Persistence环境下的对象身份
满足下列三个条件之一的,及说明两个对象表示同一身份的实体:
a、Java Indentity:内存位置相同;
b、Object Equality:经过equals()方法判断,两个对象具有相同的值;
c、Database Indentity:两个对象表示的是数据库中的同一个Row,即两个对象的Table相同,而且Primary Key相同。
四、Database Identity
Database Identity的值其实就是数据库中Row的Primary Key的值,获得Entity的Database Identity的两种方法:Entity的identifier property、Session.getIdentifier(Object entity)。
1、Entity的identifier property的可访问性
可访问性的一般规则:放开getter、关闭或者删除setter;
放开getter,是因为identifier property经常被包括持久层以外其他代码作为Entity的身份标志进行缓存,以便作为将来检索和处理该实体的依据。
关闭setter,是因为在Hibernate的绝大部分使用场景中,identifier property都是被Hibernate根据一定策略自动赋予的,而且一旦赋予就不容许修改(如果在业务场景中允许修改,说明你选择了错误的备选键)。
2、Mapping the identifier property
这里只考虑单列主键,而不考虑复合主键。
a、Hibernate XML格式:
<id name=”id” column=”ID” type=”long”>
<generator class=”native”>
</id>
说明:
type说明的是使用当前Dialect包中的何种类型转换器。
generator的策略后面会有章节专门介绍。
b、JPA Annotation
@Entity
@Table(name=”CATEGORY”)
public class Catagory{
private Long id;
……
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = “ID”)
public Long getId(){
return this.id;
}
private void setId(Long id){
this.id = id;
}
}
说明:
上面的注解可以直接在字段上使用,而不是在getter上;
而@Id注解的位置决定了其他所有的实体访问使用字段还是属性;
如果属性的名字与列名相同,那么Column注解可以省略;
上面的主键生成策略,也会在以后的章节专门讲解。
c、JPA XML Discriptors
属于20%重点以外的知识,什么时候用到了,再去先学即可。
2010年5月12日继续---又忘了买咖啡了
3、主键的映射
a、备选键和主键
备选键是能够用来唯一标志一个row的列或者列的集合。备选键只有满足以下条件后,才有资格成为主键:
- 值永远不为空;
- 每一行中该键的值唯一;
- 该键的值一旦设定,就不能再更改。(不然对其的外键引用将面临复杂的修改)
从备选键中选择一个最合适的作为primary key,将其他备选键设置成unique keys。
b、自然主键Nature key
自然主键:使用具有business meaning的属性或者属性的集合作为主键,比如将身份证号作为User表的逐渐。
自然主键可能带来各种问题:考虑到上面的主键选择条件,实体中几乎没有那个属性满足以上要求,即使满足也会因为不能被很好的index而影响效率;而且往往需要组合多个属性来成为主键,这又增加了查询、维护等的复杂性。
因此,自然主键一般只存在于遗留系统中,目前的数据库设计实践中都推荐使用synthetic identifier虚拟主键,即没有业务任何业务意义的key。synthetic identifier一般由database或者application自动生成,应用程序的用户不知道它们的存在,它们隐藏在系统内部。
4、Hibernate中synthetic identifier的生成策略
这里只列出企业级应用中常用的三种选择,其他生成策略简单了解即可
Hibernate | Jpa | 备注 |
native | AUTO | 根据底层数据库的特性,自动选择使用Identity或者sequence机制 |
identity | IDENTITY | 适合支持Identity列的数据库,返回值是整数。 |
sequence | SEQUENCE | 适合支持序列机制的数据库,使用sequence参数指定实际使用的序列的名字,返回值是整数。 |
举例:
在Hibernate XML中为生成器传递参数:
<id name=”id” column=”ID”>
<generator class=”sequence”>
<parameter name=”sequence”>MY_SEQUENCE</parameter>
<parameter name=”parameters”>
INCRENMENT BY 1 START WITH 1
</parameter>
</generator>
</id>
在JPA的注解中使用Hibernate自定义的生成器:
@Entity
@org.hibernate.annotations.GenericGenerator(
name=”hibernate-uuid”,
strategy=”uuid”
)
class MyEntity{
@Id
@GeneratedValue(generator=”hibernate-uuid”)
@Column(name=”ID”)
private String id;
}
上面的两种逻辑可以看出,Generator其实可以用在id以外的其他任意字段,为其自动生成值。
在JPA的例子中在Entity级别声明的Generator可以被实体的任何属性拿来自动生成值。在JPA的XML格式的映射文件中还可以定义全局的Generator,当然在Entity级别声明的Generator可以override全局级别定义的Generator。
在特殊的需要中,你可以参考Hibernate的源代码实现IdentifierGenerator接口来创建自己的生成器,这在以前的西部管道的应用中有见到过。
一种最佳实践建议是,对一个Domain Model中的所有实体采用相同的Identifier生成策略。
复合主键多见于遗留系统中,在遇到这种情况是请参考原书,本次学习中不将其作为重点。
五、Class mapping options
1、动态生成SQL
默认的情况下,Hibernate是在每次Startup时,生成好所有的CRUD SQL语句。这样做的优点是运行时SQL可以快速的生成,提高性能;但是在某些情况下,这种机制反而会使性能降低。举例来说,假如现在你需要更新一个有好几百个列的表格的一行,默认情况下,Hibernate在Startup期间生成的SQL语句,无法预知哪些字段需要更新,因此执行的策略就是所有未更新的列也都出现在更新语句中,效果可想而知。
Hibernate为上述情况提供了两种实体类级别的配置选项,用来在运行时根据实体的具体状态实时生成SQL语句:
<class name=”Item” dynamic-insert=”true” dynamic-update=”true”>
…
</class>
或者JPA下:
@Entity
@org.hiberante.annotations.Entity(
dynamicInsert=true,dynamicUpdate=true
)
public class Item{
…
}
说明:
dynamic-insert=”true’ 时,只为值不为null的property生成insert语句;
dynamic-update=”true”时,只为值未发生改变的property生成update语句;
2、将一个实体声明成不可改变的
某些实体可能不需要更新操作,因此不需要为其生成UPDATE语句,不需要进行dirty checking等等。这时可以将其声明为不可变的,提升性能。
<class name=”Bid” mutable=”false”>
…
</class>
一个POJO如果是不可变的,一般的做法是关闭所有的setter方法,只在构造器中提供设置字段值的途径。
@Entity
@org.hibernate.annotations.Entity(mutable=false)
@org.hibernate.annotations.AcessType(“field”)
public class Bid{
…
}
注意AccessType强制Hibernate通过field来访问和设置实体的值,不过一般不这么用,Hibernate会检测@Id所在的位置,进而推测访问策略。
2010年5月13日部门聚餐,喝酒喝高了,脑子晕了,就作为习惯性的动作,看一点,整理一点….
3、关闭自动导入
在使用HQL时,我们一般都会直接使用实体类的名字,而不用完整的限定实体类的完整路径(包含包路径),这时因为Hibernate会自动把实体类所在的package自动引入,从而完成实体类路径的完整限定。但是在某些特殊的情景下,比如在同一个SessionFactory管理的两个Package下有两个Name相同的类,这个时候就需要完整的限定实体类的路径,关闭Hibernate的自动导入。使用<hibernate-mapping>元素的import=”false”属性来关闭自动导入,使用完整限定名;或者使用<hibernate-mapping>的<import class=”com.handlewell.Auditable” rename=”IAuditable”/>来重命名实体类。如果使用JPA,使用@Entity(name=”IAuditable”)限定实体类,也可以达到同样的效果。
注:像其他直接设置给<hibernate-mapping>的子元素一样,<import>元素是Application全局性的特性,一旦设置影响这个程序,不用在其他映射文件重复设置。
哎呀妈呀,不行了,在这样下去,指不定打出什么样的文字呢。。。今天到此为止吧,再继续我就得倒在键盘上了----这喝酒真是再不能冲动了,不管你有多少酒量。。。
2010年5月14日 LiuHeng来北京,陪他和XiaoYan到10点半,今天学习搁浅。
2010年5月15日 送Y头到家,自己往回返的时候没赶上最后一班地铁,到家0点了,今日学习搁浅
2010年5月16日 和Y头一起待了一晚上,晚上到家11点,11点准时休息,今日学习搁浅
2010年5月17日
4、声明一个包名
使用:在一个映射文件的<hibernate-mapping>节点中,设置属性package=”com.company.module.persistence”
效果:在此映射文件中出现的所有unqualified的class都会自动使用Package属性声明的包名,作为自己的前缀。
这个属性的应用频率是非常高的。
5、其他功能:
让标识符中含有特殊字符、实现命名规范限定