`
jiasky
  • 浏览: 23343 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

如何扩展 JPA Annotation 以更好的支持 OR Mapping

阅读更多

原文摘自IBM开发者论坛:http://www.ibm.com/developerworks/cn/java/j-lo-jpa-anntation/index.html

 

JPA 和 OpenJPA 简介

随着面向对象的数据模型被广泛应用,将面向对象的实体映射为关系数据库表项(OR Mapping)已经越来越多的出现在各类应用程序的开发设计之中。JPA(Java Persistence API)是 Sun Microssystems 公司制定的一个把 Java 数据对象映射成关系数据库对象的一个标准。JPA 弥补了 JDBC、ORM、EJB2 等在 Java 对象持久化方面的不足之处,并且非常易于使用。JPA 因大量使用了像 annotations 和 generics 等 Java 语言的新特性,需要 J2SE 1.5(也被称作 Java 5)或者更高版本的支持。Apache 的 OpenJPA 则是当前诸多热门 Java 持久化实现之一。Apache OpenJPA 是 Apache 软件基金下面的一个 Java 持久化技术,它可以被当作一个单独的 POJO(Plain Old Java Object)持续化使用,或者被集成到任意 Java EE 兼容的容器或其它的轻量级框架(例如 Tomcat、Spring)等一起使用。


标准 JPA annotation 及其在 OpenJPA 中的应用

标准 JPA 提供了不少 annotation,在 OpenJPA 中这些标准的 annotation 的用法如下:

@entity

@entity 用来声明一个类为一个持久化的实体类。该 annotation 只有唯一可选的属性,即 name 用于指定实体的名称。

@table

@table 描述了一个实体类对应的数据库表的信息。@table 有以下属性:

  • String name: 表的名称。
  • String schema: 该表对应的 schema,如果没有指定 schema 的名称,JPA 在数据库连接时使用默认的 schema。
  • String catalog: 表的目录。如果没有指定目录,JPA 在数据库连接时使用默认值。
  • UniqueContraint[] uniqueContraints: 应用于表的唯一性限制条件。Uniquecontraint 本身是个 annotation, 用于保证数据库表的某一列(简单数据类型或者复杂数据类型)在每一个记录中都是唯一的。

@column

@column 定义了数据库表项的每一列的属性,具体内容包括:

  • String name: 指定列的名称,默认值为类的 field 名称。
  • String columnDefinition: 数据库系统特定的列类型名称。在 OpenJPA 中,该属性只适用于数据库系统通过那些能够从元数据直接创建表的数据库系统。在表的创建过程中,如果该属性值非空,那么数据库系统就以该属性指定的类型来定义指定的列。
  • int length:指定列的长度。
  • int precision:小数列的精度。
  • int scale:定义小数类型的列最多可以容纳的数字数目
  • boolean nullable: 该列是否能够接受非空的记录。
  • boolean insertable:定义该列是否可以通过 SQL INSERT 语句中指定生成,默认值为 true。
  • boolean updatable:定义该列是否可以通过 SQL UPDATE 语句中生成,默认值为 true。
  • String table:有的时候列并非定义在主表下,table 属性就提供了将列映射到次表下的方法。

@ Inheritance

在面向对象的类之间存在继承关系,@inheritance 的出现就将继承关系能够映射到关系数据库模型中。在实体基类中定义继承策略

InheritanceType stragegy:用以声明继承策略的枚举类型,可取的值包括 SINGLE_TABLE,JOINED 和 TABLE_PER_CLASS:

  • InheritanceType.SINGLE_TABLE:把所有的子类及其父类都映射到同一张表中。
  • InheritanceType.JOINED: 类图中每个类都映射到各自的表项,但是每张表中仅仅包含在各个层次的类中定义的列。
  • Inheritance.TABLE_PER_CLASS:类图中每个类都映射到各自的表现,和 JOINED 不同之处在于每张表包含了对应的实体类所有的列。


清单 1. JPA annotation 使用范例

@Entity(name="OperatingSystem") 
@Table(name="OperatingSystem") 
@Category(value="System") 
@Inheritance(strategy=InheritanceType.JOINED) 
public class OperatingSystem extends System{ 
…
/** 
     * A string describing the operating system version number. 
     */ 
    @Column(name="OSVersion",length=64) 
    @Basic 
    private String OSVersion; 
…
}

 

扩展的 annotation 及其使用

在实际应用中,JPA 已有的 annotation 往往不能满足 OR 映射的所有需求。以下介绍通过自定义 Annotation 来扩充 OpenJPA 功能的方法,从而可以更好满足实际应用的需求。

@AbstractWithTable

在 OpenJPA 中,针对实体之间的继承关系如何在数据库中展现,共有三种方式:

SINGLE_TABLE,TABLE_PER_CLASS 和 JOINED。

当应用程序使用 TABLE_PER_CLASS 这种方式时,Java 的抽象类(Abstract Class)在 OpenJPA 中不会被映射到数据库中的表项,同时抽象性阻止了用户不能通过 OpenJPA 直接对该类进行持久化的操作。有些应用需要保持一个类在持久化操作方面的抽象性,但是又能保证该类在数据库中有对应的表项。那么该类必须被定义为 Java 的具体类,同时引入 @AbstractWithTable(如清单 2):


清单 2. @AbstractWithTable 的定义

@Target({ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface AbstractWithTable { 

} 
 

@AbstractWithTable 的使用方式如清单 3 所示:


清单 3. 应用 @AbstractWithTable 定义实体类

@AbstractWithTable 
@Entity 
@EntityListeners(value = { TestAWTListener.class }) 
public class TestAWT { 
	
    @Basic 
    public String p; 
	
}
 

从清单 3 中可见,除了 @Entity 之外,@AbstractWithTable 和 @EntityListeners 也被应用到类 TestAWT 中。其中 @AbstarctWithTable 就是用来指定这个抽象类需要一个数据库表,同时类 TestAWT 并没有使用 abstract 这个 Java 关键字。

OpenJPA 提供了 @EntityListeners 指定进行持久化操作前后的回调函数。定义 TestAWT 的回调函数如下清单 4 所示。


清单 4. 处理 @AbstractWithTable 的回调函数

public class TestAWTListener { 
    @PrePersist 
    public void prePersist(Object obj) throws Exception{ 
        AbstractWithTable awt = obj.getClass().getAnnotation(AbstractWithTable.class); 
        if (awt != null) { 
            System.out.println("abstract class cannot be persisted"); 
            throw new Exception(); 
        } 
    } 
} 

 

在回调函数中检测相关对象是否设置了 @AbstractWithTable,如果 @AbstractWithTable 被应用到某个类,异常将被抛出来从而阻止将此对象存入数据库中。这样通过 @AbstractWithTable 和回调函数就达到了控制类的 JPA 的持久化操作权限和该类能映射到数据库的表项的目的。

@MaximumValue 和 @MinimumValue

数据库中最常见的情况就是对某个字段的取值范围定义约束条件(Constraint),比如学生的考试成绩取值范围必须在 0 到 100 这个范围内。但是 OpenJPA 没有提供相应的 Annotation 来指定某个属性的取值范围。

为了解决这个问题,自定义的 Annotation:@Maximum 和 @Minimum 被引入,如下清单 5 和 6 所示。它们分别用来表示最大值和最小值。


清单 5. @MaximiumValue 和 @MinimiumValue 的定义

 

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MaximumValue {
    String value();
}

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface MinimumValue {
    String value();
}
 

 

 

清单 6. 应用 @Maximium 和 @Minimium

@Entity
@EntityListeners(value = { TestMaxMinListener.class })
public class TestMaxMin {

    @Basic
    @MinimumValue("1")
    @MaximumValue("100")
    public Integer value;

}
 

@MaximiumValue 和 @MinimiumValue 被应用于 Java 类的域中。

与 3.1 类似,在这里设置了回调函数所在的类 TestMaxMinListener,函数如清单 7 所示:


清单 7. 处理 @ MaximiumValue 和 @MinimiumValue 的回调函数

public class TestMaxMinListener {

    @PrePersist
    public void prePersist(Object obj) throws Exception {
		
        TestMaxMin tmm = (TestMaxMin)obj;
        Field value = tmm.getClass().getDeclaredField("value");
        MaximumValue max = value.getAnnotation(MaximumValue.class);
        MinimumValue min = value.getAnnotation(MinimumValue.class);
		
        if (tmm.value > max.value() || tmm.value < min.value()){
            System.out.println("The value property is invalid.");
            throw new Exception();
        }
    }
}
 

这个 TestMaxMinListener 的作用就是检测相应的属性值是否在规定的范围内,如果超出了规定的范围,那么就会抛出异常,从而防止将错误的数据存入数据库中。

@Unsigned

与上面的情况相同,对于某些属性的值,我们希望它是一个无符号整形数。而在 OpenJPA 中也没有相关的 Annotation 来指定。可以自定义一个 Annottation:@Unsigned 用来表示某个属性的值是无符号数。


清单 8. @Unsigned 的定义

@Target({ElementType.FIELD}) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface Unsigned { 

}
 

在 Entity Class 当中可以直接把 @Unsigned 设置在相应的属性前面 , 如清单 9 所示:


清单 9. 应用 @Unsigned

@Unsigned 
@Basic 
private Integer value; 
 

@Unsigned 的处理方式和 @MaximiumnValue 类似,这里就不再一一赘述。

@Required

数据库对表中的各项字段可以定义限制条件,如某些字段不能为空,某些字段为主键等。OpenJPA 定义的 @Column 中提供了字段以限制条件的映射,参考清单 11 中 @Column,将 nullable 设置为 false 来控制字段不能为空。

但是在图 1 所示的应用中,OnlineGame.ServiceProvider 不能为 null,同时必须 SINGLE_TABLE 的继承策略(参照表 1)来映射这一组类,如果仅仅应用 @Column 的 nullable 属性,我们将不得不面临这样一个问题:当试图持久化一个 PCGame 的实例到数据库中时,数据库系统将会提示该数据不能插入,因为数据系统对插入表 1 中的数据要求 ServiceProvider 不能为空。


图 1. 类图

 

表 1 . 用 SINGLE_TABLE 在 DB 中存储 Game,OnlineGame 和 PCGame

 

Game 的属性 OnlineGame 的属性 PCGame 的属性
Name Version Type
ServiceProvider
...

 

难道为了应用 SINGLE_TABLE 就不得不丢弃字段的约束条件么? @Required 就应运而生了,定义如清单 10。@Required 被用来标记 p1,p2 不能为 null。

就需要在我们自己的程序里面检测 @Required,并对相应的属性值进行判断。这一步通常可以放在 EntityListener 中的 PrePersist 这个回调(Callback)函数中进行检测。


清单 10. @Required 的定义

@Target({ElementType.FIELD}) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface Required { 

} 

在 OnlineGame Class 中,可以通过如下清单 11 所示方式设置属性 ServiceProvider:


清单 11. 应用 @Required

@Column(name="",nullable=true) 
@Basic 
@Required 
private String ServiceProvider; 
 

数据库不再对 ServiceProvider 是否为空做出约束,但是通过实现标记为 @prePresist 的回调函数,函数中检测是否有 @Required,如果有 @Required,该字段就不能为空。如此 ServiceProvider 的约束条件就不会再成为约束类 PCGame 的条件。

@RelationshipMetaData

在 OpenJPA 中,有四个 Annotation 可以用来标识一个实体之间的关系,它们分别是:@OneToOne,@OneToMany,@ManyToOne 和 @ManyToMany。其中每一个 Annotation 都有以下几个属性:targetEntity,mappedBy,cascade,fetch 和 optional。


图 2. 实体 Employee 和 SoftwareLicense 的 ER 图

 

尽管包括了一对一、一对多和多对多的情况,但是为了将图 2 所示 ER 关系图映射到关系数据库,以上的 Annotation 就不能满足要求。图 2 的关系 Owns 是一个多对多的关系,Owns 包括了两个属性 RequestDate 和 ExpiredDate,Owns 必须对应到一个实体类。类 Employee、SoftwareLicense 和 Owns 之间分别存在着多对一的关系,@ManytoOne 或者 @OnetoMany 虽然能够标识出关系,但是并不能标识出关系的源或者目的是一个实体还是关系,@RelationshipMetaData 就有了用武之地,其定义如清单 12 所示:


清单 12. @RelationshipMetaData 的定义

 

@Target({ElementType.FIELD}) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface RelationshipMetaData { 
    boolean isSource(); 
    boolean isManyToManyWithProperties(); 
} 
 

有了 @RelationshipMetaData,图 2 对应的实体就可以被定义为:


清单 13. 应用 @RelationshipMetaData 实现图 2 的 OR 映射

@Table(name="Employee")
@Entity(name="Employee")
public class Employee {
    …
    @RelationshipMetaData(isSource=true,isManyToManyWithProperties=true)
    @OneToMany(mappedBy=”Source”)
    private java.util.Collection<Employee_owns_SoftwareLicense> owns_SoftwareLicense;
    …
};

@Table(name="SoftwareLicense")
@Entity(name="SoftwareLicense")
public class SoftwareLicense{
    …
    @RelationshipMetaData(isSource=false,isManyToManyWithProperties=true)
    @OneToMany(mappedBy=”Target”)
    private java.util.Collection<Employee_owns_SoftwareLicense> Employee_owns;
    …
}

@Table(name=" Employee_owns_SoftwareLicense ")
@Entity(name=" Employee_owns_SoftwareLicense ")
public class Employee_owns_SoftwareLicense{
    …
    @ManyToOne
    private Employee Source;
    
    @ManyToOne
    private SoftwareLicense Target;
    …
}
 

总结

本文首先介绍了 JPA 和 Apache OpenJPA,以及它们在将面向对象的实体映射为关系数据库表项 (OR Mapping) 方面的应用。然后介绍了 JPA 提供的标准 Annotations, 并且举例介绍了其用途。但实践表明已有的 annotation 常常不能满足需求,结合笔者的经验,一些扩展的常用的 annotation 以更好的满足 OR Mapping 的需求。通过提供自定义的 annotation,不仅弥补了目前 OpenJPA 提供的标准 annotation 的功能缺陷,而且提升了用户对 OR Mapping 过程的自适应调节和更灵活的控制,对于满足有特殊 OR Mapping 需求的开发具有非常实用的价值。

 

 

 

分享到:
评论

相关推荐

    SpringIOC_SpringMVC_SpringAnnotation_JPA

    Spring Data JPA是一个扩展,提供了更高级的功能,如查询生成器,简化了对数据库的操作。`@Entity`标记实体类,`@Table`指定表名,`@Id`标识主键,`@GeneratedValue`定义主键生成策略等。 在实际开发中,这四个组件...

    JPA和Hibernate的关系

    - **Hibernate**:Hibernate是一个实现了JPA规范的具体框架,同时提供了扩展功能,如更强大的查询语言(HQL)、缓存策略等。 2. **实现机制**: - Hibernate通过几个关键组件来实现对JPA的支持: - **hibernate-...

    JPA视频教程_使用jpa映射单个实体对象

    4. **关系映射(Relationship Mapping)**: JPA也支持处理对象之间的关系,如一对一(OneToOne)、一对多(OneToMany)、多对一(ManyToOne)和多对多(ManyToMany)。例如,一个User可能有多个Order,可以这样映射...

    JPA配置需要的jar

    JPA通过ORM(Object-Relational Mapping)机制将Java类与数据库表映射,简化了数据库应用的开发。 在配置JPA时,通常需要以下关键的jar文件: 1. **Java Persistence API (JPA)**:这是核心API,提供了基本的接口...

    JPA开发文档API

    ORM框架如Hibernate、EclipseLink和JPA等,允许开发者以面向对象的方式处理数据,无需关心底层的SQL细节。 2. **JPA体系架构** - JPA提供了一种标准的API和元数据模型,用于定义Java对象如何映射到数据库表。这...

    JPA搭建环境所需的jar包

    JPA通过Hibernate等实现器提供了强大的ORM(Object-Relational Mapping)功能,简化了数据库操作。本篇将详细讲解如何利用Hibernate搭建JPA环境,并介绍所需的jar包。 首先,我们需要了解JPA的核心概念。JPA允许...

    SpringMVC4+JPA Demo

    **SpringMVC 4 + JPA 整合详解** 在现代Java Web开发中,SpringMVC作为主流的MVC框架,常与Java Persistence API (JPA) 集成,用于处理数据持久化。本Demo将详细介绍如何在SpringMVC 4项目中整合JPA,实现高效的...

    hibernate-Annotation.jar

    同时,Hibernate 4.x和5.x版本还引入了更多的特性,如更完善的缓存机制、更好的性能优化以及对JPA 2.0标准的支持等。 综上所述,Hibernate Annotation使得Java开发者能够更高效地管理数据库操作,减少XML配置,提高...

    注解、JPA、应用集成

    对于提供的压缩包文件,其中可能包含关于这些主题的示例代码、教程文档或者案例研究,通过研究这些资料,你将能更好地理解和掌握注解、JPA和应用集成在实际开发中的应用。如果遇到任何问题,也可以通过邮件348977787...

    spring springmvc jpa配置文件

    在IT行业中,Spring框架是Java开发中的核心组件,它提供了丰富的功能来简化应用程序的构建。Spring MVC是Spring框架的一部分,...在实际项目中,这些配置文件通常会被更复杂和详细的设置所替换,以满足特定的业务需求。

    D_JPA_01_环境搭建

    这些依赖包含了Spring Data JPA,它是对JPA的扩展,提供了更高级的功能和友好的API,以及MySQL数据库驱动,因为大多数开发环境会用到。 #### 步骤2:配置数据库连接 在`application.properties`或`application.yml`...

    sping hibernate Annotation(注释配置) demo(例子)

    同时,我们可以通过 `SessionFactory` 创建的 `Session` 对象,或者利用 Spring Data JPA 提供的 `JpaRepository` 接口来执行数据库操作。 至于 Flex,它是一个用于构建富互联网应用程序(Rich Internet ...

    Hibernate Annotation笔记

    为了更好地理解Hibernate Annotation是如何工作的,我们可以通过以下步骤来设置一个基本的环境: 1. **安装和配置Hibernate Annotation** - **添加依赖库**:首先,确保项目中包含Hibernate 3.2及以上版本的库。...

    JPA 批注参考

    对于不直接支持的数据类型,可以通过`@Converter`自定义转换逻辑,将非标准类型转换为JPA可识别的类型。 以上就是JPA批注的一些核心知识点,它们极大地简化了Java应用与数据库之间的交互,使得开发更加高效。在...

    Hibernate中文文档+hibernate annotation +distribution

    其次,hibernate-annotations-3.4.0GA是Hibernate支持JPA(Java Persistence API)的重要组成部分,引入了注解方式来声明对象与数据库表之间的映射关系。例如,`@Entity`表示一个类作为数据库中的表,`@Table`定义...

    Spring集成JPA配置懒加载报错解决方案

    懒加载是JPA的特性,它可以在需要时才加载关联对象,以提高应用程序的性能。 报错一:no session 懒加载报错的第一个问题是no session,因为EntityManager对象在事务提交后就关闭了。解决办法是配置一个过滤器,使...

    从最基本的Java工程搭建SpringMVC+SpringDataJPA+Hibernate

    ing 相关版本号 --&gt; &lt;spring.version&gt;5.3.23&lt;/spring.version&gt; &lt;springmvc.version&gt;5.3.23&lt;/springmvc.version&gt; &lt;springdata.jpa....理解这些步骤有助于你更好地掌握Spring全家桶的使用,并能灵活地应用于实际项目中。

    JPA:关于JPA的一切

    Java持久化API(Java Persistence API,简称JPA)是Java平台上的一个ORM(对象关系映射)标准,它为开发者提供了在Java应用中管理和存储数据的框架。...通过理解和熟练运用上述知识点,可以更好地在实际项目中应用JPA。

Global site tag (gtag.js) - Google Analytics