Mapping Persistence Classes

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、其他功能:

让标识符中含有特殊字符、实现命名规范限定

posted @ 2010-05-10 20:28  Enjoy Everyday  阅读(318)  评论(0编辑  收藏  举报