Java Web2.0项目开发进阶---Hibernate 对标注的支持,用EJB3注解进行映射

 

现在EJB3实体Bean是纯粹的POJO.实际上这表达了和Hibernate持久化实体对象同样的概念. 它们的映射都通过JDK5.0注解来定义(EJB3规范中的XML描述语法至今还没有最终定下来). 注解分为两个部分,分别是逻辑映射注解和物理映射注解, 通过逻辑映射注解可以描述对象模型,类之间的关系等等, 而物理映射注解则描述了物理的schema,,,索引等等. 下面我们在代码中将混合使用这两种类型的注解.

EJB3注解的API定义在javax.persistence.*包里面. 大部分和JDK5兼容的IDE(Eclipse, IntelliJ IDEA Netbeans等等)都提供了注解接口和属性的自动完成功能. (这些不需要IDE提供特别的EJB3支持模块,因为EJB3注解是标准的JDK5注解)

请阅读JBoss EJB 3.0指南或者直接阅读Hibernate Annotations测试代码以获取更多的可运行实例.Hibernate Annotations提供的大部分单元测试代码都演示了实际的例子,是一个获取灵感的好地方.

12.13.1  声明实体bean

每一个持久化POJO类都是一个实体bean,这可以通过在类的定义中使用@Entity注解来进行声明:

@Entity

public class Flight implements Serializable {

    Long id;

    @Id

    public Long getId() { return id; }

 

    public void setId(Long id) { this.id = id; }

}

通过@Entity注解将一个类声明为一个实体bean(即一个持久化POJO), @Id注解则声明了该实体bean的标识属性. 其他的映射定义是隐式的.这种以隐式映射为主体,以显式映射为例外的配置方式在新的EJ3规范中处于非常重要的位置, 和以前的版本相比有了质的飞跃. 在上面这段代码中:Flight类映射到Flight,并使用id列作为主键列.

在对一个类进行注解时,你可以选择对它的的属性或者方法进行注解,根据你的选择,Hibernate的访问类型分别为 fieldproperty. EJ3规范要求在需要访问的元素上进行注解声明,例如,如果访问类型为 property就要在getter方法上进行注解声明, 如果访问类型为 field就要在字段上进行注解声明.应该尽量避免混合使用这两种访问类型. Hibernate根据@Id @EmbeddedId的位置来判断访问类型.

12.13.2  定义表

@Table是类一级的注解, 通过@Table注解可以为实体bean映射指定表(table),目录(catalog)schema的名字. 如果没有定义@Table,那么系统自动使用默认值:实体的短类名(不附带包名).

@Entity

@Table(name="tbl_sky")

public class Sky implements Serializable {

...

@Table元素包括了一个schema 和一个 catalog属性,如果需要可以指定相应的值. 结合使用@UniqueConstraint注解可以定义表的唯一约束(unique constraint) (对于绑定到单列的唯一约束,请参考@Column注解)

@Table(name="tbl_sky",

            uniqueConstraints = {@UniqueConstraint(columnNames={"month", "day"})}

        )

上面这个例子中,monthday这两个字段上定义唯一约束. 注意columnNames数组中的值指的是逻辑列名.

HibernateNamingStrategy的实现中定义了逻辑列名. 默认的EJB3命名策略将物理字段名当作逻辑字段名来使用. 注意该字段名和它对应的属性名可能不同(如果字段名是显式指定的话). 除非你重写了NamingStrategy,否则不用担心这些区别..

12.13.3  乐观锁定版本控制

你可以在实体bean中使用@Version注解,通过这种方式可添加对乐观锁定的支持:

@Entity

public class Flight implements Serializable {

...

    @Version

    @Column(name="OPTLOCK")

    public Integer getVersion() { ... }

} 

上面这个例子中,version属性将映射到 OPTLOCK, entity manager使用该字段来检测更新冲突(防止更新丢失,请参考last-commit-wins策略).

根据EJB3规范,version列可以是numeric类型(推荐方式)也可以是timestamp类型. Hibernate支持任何自定义类型,只要该类型实现了UserVersionType.

12.13.4  属性映射

1.声明基本的属性映射

实体bean中所有的非statictransient的属性都可以被持久化, 除非你将其注解为@Transient.所有没有定义注解的属性等价于在其上面添加了@Basic注解. 通过 @Basic注解可以声明属性的获取策略(fetch strategy)

public transient int counter; //transient property

private String firstname; //persistent property

@Transient

String getLengthInMeter() { ... } //transient property

String getName() {... } // persistent property

@Basic

int getLength() { ... } // persistent property

@Basic(fetch = FetchType.LAZY)

String getDetailedComment() { ... } // persistent property

@Temporal(TemporalType.TIME)

java.util.Date getDepartureTime() { ... } // persistent property          

@Enumerated(EnumType.STRING)

Starred getNote() { ... } //enum persisted as String in database

上面这个例子中,counter是一个transient的字段, lengthInMetergetter方法被注解为@Transient, entity manager将忽略这些字段和属性. name,length,firstname 这几个属性则是被定义为可持久化和可获取的.对于简单属性来说,默认的获取方式是即时获取(early fetch). 当一个实体Bean的实例被创建时,Hibernate会将这些属性的值从数据库中提取出来,保存到Bean的属性里. 与即时获取相对应的是延迟获取(lazy fetch).如果一个属性的获取方式是延迟获取 (比如上面例子中的detailedComment属性), Hibernate在创建一个实体Bean的实例时,不会即时将这个属性的值从数据库中读出. 只有在该实体Bean的这个属性第一次被调用时,Hibernate才会去获取对应的值. 通常你不需要对简单属性设置延迟获取(lazy simple property),千万不要和延迟关联获取(lazy association fetch)混淆了 (译注:这里指不要把lazy simple propertylazy association fetch混淆了).

注意

为了启用属性级的延迟获取,你的类必须经过特殊处理(instrumented) 字节码将被织入原始类中来实现延迟获取功能, 详情参考Hibernate参考文档.如果不对类文件进行字节码特殊处理, 那么属性级的延迟获取将被忽略.

推荐的替代方案是使用EJB-QL或者Criteria查询的投影(projection)功能.

HibernateEJB3都支持所有基本类型的属性映射. 这些基本类型包括所有的Java基本类型,及其各自的wrapper类和serializable. Hibernate Annotations还支持将内置的枚举类型映射到一个顺序列(保存了相应的序列值) 或一个字符串类型的列(保存相应的字符串).默认是保存枚举的序列值, 但是你可以通过@Enumerated注解来进行调整(见上面例子中的note属性).

在核心的Java API中并没有定义时间精度(temporal precision). 因此处理时间类型数据时,你还需要定义将其存储在数据库中所预期的精度. 在数据库中,表示时间类型的数据有DATE, TIME, TIMESTAMP三种精度(即单纯的日期,时间,或者两者兼备). 可使用@Temporal注解来调整精度.

@Lob注解表示属性将被持久化为Blob或者Clob类型, 具体取决于属性的类型, java.sql.Clob, Character[], char[] java.lang.String这些类型的属性都被持久化为Clob类型, java.sql.Blob, Byte[], byte[] serializable类型则被持久化为Blob类型.

@Lob

public String getFullText() {

    return fullText;

}

@Lob

public byte[] getFullCode() {

    return fullCode;

}

如果某个属性实现了java.io.Serializable同时也不是基本类型, 并且没有在该属性上使用@Lob注解, 那么Hibernate将使用自带的serializable类型.

2.声明列属性

使用 @Column 注解可将属性映射到列. 使用该注解来覆盖默认值(关于默认值请参考EJB3规范). 在属性级使用该注解的方式如下:

不进行注解

@Basic一起使用

@Version一起使用

@Lob一起使用

@Temporal一起使用

@org.hibernate.annotations.CollectionOfElements一起使用 (只针对Hibernate )

@Entity

public class Flight implements Serializable {

...

@Column(updatable = false, name = "flight_name", nullable = false, length=50)

public String getName() { ... }

在上面这个例子中,name属性映射到flight_name. 该字段不允许为空,长度为50,并且是不可更新的(也就是属性值是不变的).

上面这些注解可以被应用到正规属性上例如@Id @Version属性.

@Column(

    name="columnName";                                (1)

    boolean unique() default false;                   (2)

    boolean nullable() default true;                  (3)

    boolean insertable() default true;                (4)

    boolean updatable() default true;                 (5)

    String columnDefinition() default "";             (6)

    String table() default "";                        (7)

    int length() default 255;                         (8)

    int precision() default 0; // decimal precision   (9)

    int scale() default 0; // decimal scale

(1) name 可选,列名(默认值是属性名)

 (2) unique 可选,是否在该列上设置唯一约束(默认值false)

 (3) nullable 可选,是否设置该列的值可以为空(默认值false)

 (4) insertable 可选,该列是否作为生成的insert语句中的一个列(默认值true)

 (5) updatable 可选,该列是否作为生成的update语句中的一个列(默认值true)

 (6) columnDefinition 可选: 为这个特定列覆盖SQL DDL片段 (这可能导致无法在不同数据库间移植)

 (7) table 可选,定义对应的表(默认为主表)

 (8) length 可选,列长度(默认值255)

 (8) precision 可选,列十进制精度(decimal precision)(默认值0)

 (10) scale 可选,如果列十进制数值范围(decimal scale)可用,在此设置(默认值0)

3.嵌入式组件

在实体中可以定义一个嵌入式组件(embedded component), 甚至覆盖该实体中原有的列映射. 组件类必须在类一级定义@Embeddable注解. 在特定的实体的关联属性上使用@Embedded @AttributeOverride注解可以覆盖该属性对应的嵌入式对象的列映射:

 

@Entity

public class Person implements Serializable {

    // Persistent component using defaults

    Address homeAddress;

    @Embedded

    @AttributeOverrides( {

            @AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),

            @AttributeOverride(name="name", column = @Column(name="bornCountryName") )

    } )

    Country bornIn;

    ...

}

 

@Embeddable

public class Address implements Serializable {

    String city;

    Country nationality; //no overriding here

}

 

@Embeddable

public class Country implements Serializable {

    private String iso2;

    @Column(name="countryName") private String name;

    public String getIso2() { return iso2; }

    public void setIso2(String iso2) { this.iso2 = iso2; }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    ...

}

嵌入式对象继承其所属实体中定义的访问类型 (注意:这可以通过使用Hibernate提供的@AccessType注解来覆盖原有值)(请参考 Hibernate Annotation Extensions).

 

在上面的例子中,实体bean Person 有两个组件属性, 分别是homeAddressbornIn. 我们可以看到homeAddress 属性并没有注解. 但是Hibernate自动检测其对应的Address类中的@Embeddable注解, 并将其看作一个持久化组件.对于Country中已映射的属性, 则使用@Embedded@AttributeOverride 注解来覆盖原来映射的列名. 正如你所看到的, Address对象中还内嵌了Country对象, 这里和homeAddress一样使用了HibernateEJB3自动检测机制. 目前EJB3规范还不支持覆盖多层嵌套(即嵌入式对象中还包括其他嵌入式对象)的列映射. 不过Hibernate通过在表达式中使用"."符号表达式提供了对此特征的支持.

@Embedded

    @AttributeOverrides( {

            @AttributeOverride(name="city", column = @Column(name="fld_city") ),

            @AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),

            @AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )

            //nationality columns in homeAddress are overridden

    } )

    Address homeAddress;

Hibernate注解支持很多EJB3规范中没有明确定义的特性. 例如,可以在嵌入式对象上添加 @MappedSuperclass注解, 这样可以将其父类的属性持久(详情请查阅@MappedSuperclass).

Hibernate现在支持在嵌入式对象中使用关联注解(@*ToOne@*ToMany). EJB3规范尚不支持这样的用法。你可以使用 @AssociationOverride注解来覆写关联列.

在同一个实体中使用两个同类型的嵌入对象, 其默认列名是无效的:至少要对其中一个进行明确声明. Hibernate在这方面走在了EJB3规范的前面, Hibernate提供了NamingStrategy, 在使用Hibernate, 通过NamingStrategy你可以对默认的机制进行扩展. DefaultComponentSafeNamingStrategy 在默认的EJB3NamingStrategy上进行了小小的提升, 允许在同一实体中使用两个同类型的嵌入对象而无须额外的声明.

如果某属性没有注解,该属性将遵守下面的规则:

 

如果属性为单一类型,则映射为@Basic

否则,如果属性对应的类型定义了@Embeddable注解,则映射为@Embedded

否则,如果属性对应的类型实现了Serializable, 则属性被映射为@Basic并在一个列中保存该对象的serialized版本

否则,如果该属性的类型为java.sql.Clob java.sql.Blob,则作为@Lob并映射到适当的LobType.

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值