Chinaunix首页 | 论坛 | 博客
  • 博客访问: 321350
  • 博文数量: 176
  • 博客积分: 2970
  • 博客等级: 少校
  • 技术积分: 1520
  • 用 户 组: 普通用户
  • 注册时间: 2008-06-11 14:35
文章存档

2011年(10)

2010年(18)

2009年(62)

2008年(86)

我的朋友
最近访客

分类: Java

2009-01-06 11:09:56

JPA技术简单介绍

  • 什么是JPA?

JPA全称为Java Persistence API Java持久化APISun公司在Java EE 5规范中提出的Java持久化接口。JPA吸取了目前Java持久化技术的优点,旨在规范、简化Java对象的持久化工作。使用JPA持久化对象,并不是依赖于某一个ORM框架。

  • 为什么要使用JAP?

在说为什么要使用JPA之前,我们有必要了解为什么要使用ORM技术。

ORM Object-Relation-Mapping,即对象关系影射技术,是对象持久化的核心。ORM是对JDBC的封装,从而解决了JDBC的各种存在问题:

    1. 繁琐的代码问题

      JDBCAPI编程访问数据库,代码量较大,特别是访问字段较多的表的时候,代码显得繁琐、累赘,容易出错。例如:PreparedStatement pstmt=con.prepareStatment("insert into account value(?,?,?,?,?,?,?,?,?)");

      ORM则建立了Java对象与数据库对象之间的影射关系,程序员不需要编写复杂的SQL语句,直接操作Java对象即可,从而大大降低了代码量,也使程序员更加专注于业务逻辑的实现。

    2. 数据库对象连接问题

      关系数据对象之间,存在各种关系,包括111对多、多对1、多对多、级联等。在数据库对象更新的时候,采用JDBC编程,必须十分小心处理这些关系,以保证维持这些关系不会出现错误,而这个过程是一个很费时费力的过程。

      ORM建立Java对象与数据库对象关系影射的同时,也自动根据数据库对象之间的关系创建Java对象的关系,并且提供了维持这些关系完整、有效的机制。

    3. 系统架构问题

      JDBC属于数据访问层,但是使用JDBC编程时,必须知道后台是用什么数据库、有哪些表、各个表有有哪些字段、各个字段的类型是什么、表与表之间什么关系、创建了什么索引等等与后台数据库相关的详细信息。

      使用ORM技术,可以将数据库层完全隐蔽,呈献给程序员的只有Java的对象,程序员只需要根据业务逻辑的需要调用Java对象的Getter Setter方法,即可实现对后台数据库的操作,程序员不必知道后台采用什么数据库、有哪些表、有什么字段、表与表之间有什么关系。

    4. 性能问题

      采用JDBC编程,在很多时候存在效率低下的问题。

       

      pstmt =conn.prepareStatement("insert into user_info values(?,?)");
             for (int i=0; i<1000; i++) {
                pstmt.setInt(1,i);
                pstmt.setString(2,"User"+i.toString());
                pstmt.executeUpdate();
             }

      以上程序将向后台数据库发送1000SQL语句执行请求,运行效率较低。

      采用ORM技术,ORM框架将根据具体数据库操作需要,会自动延迟向后台数据库发送SQL请求,ORM也可以根据实际情况,将数据库访问操作合成,尽量减少不必要的数据库操作请求。

       

JPA是目前比较流行的一种ORM技术之一,所以他拥有ORM技术的各种特点,当然他还有自己的一些优势:

  1. 标准化
    JPA JCP 组织发布的 Java EE 标准之一,因此任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问 API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
  2. 对容器级特性的支持
    JPA 框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
  3. 简单易用,集成方便
    JPA的主要目标之一就是提供更加简单的编程模型:在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity进行注释;JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。JPA基于非侵入式原则设计,因此可以很容易的和其它框架或者容器集成。
  4. 可媲美JDBC的查询能力
    JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQLJava Persistence Query Language),JPQLEJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOINGROUP BYHAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
  5. 支持面向对象的高级特性
    JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
  • 怎么使用JPA?

简单说来要使用JPA需要网工程里面导入jar(不同的提供商提供的jar包也不同),并且还要有一个persistence.xml文件来设置持久性单元。

 如果你打算在你的应用中使用JPA,你就需要使用持久性单元(Persistence Units)。持久性单元具有唯一的名称,负责定义应用中的一组实体如何进行管理和持久性。在应用中使用persistence.xml文件来设置持久性单元,可以配置多个持久性单元,但每个单元拥有唯一的名称。

注意persistence.xml文件的位置决定持久性的根(Persistence Root)。持久性的根为JAR文件或者包含META-INF目录(前提是persistence.xml位于此)的目录。一般将这个persistence.xml文件放在src下的META-INF中。

 

持久性单元包含的属性有:

    • 在该持久性单元范围(作用域)内的实体类
    • 为上述实体类提供持久性的持久性提供者(Persistence Provider)或库
    • 上述实体类的数据源(Data Source)
    • 应用使用的事务类型(Transaction Type)
  • 持久性提供者(Persistence Provider

    持久性提供者指的是JPA的实现。持久性提供者是一个能够为应用提供持久性对象的的库。例如Netbeans绑定了TopLink Essentials 作为持久性提供者,同时TopLink Essentials也是Sun Java System Application Server Platform Edition 9 (Glassfish)的参考实现和默认配置。TopLink Essentials包括toplink-essentials.jartoplink-essentials-agent.jar两个JAR文件。你可以使用别的持久性提供者例如Hibernate 
  • 数据源

    数据源指的是提供实体存储的数据库。数据源必须在服务器中注册并且使用JNDI名称指定。如果事务是由容器管理的JTA事务那么数据源必须是JTA数据源。如果事务是应用负责管理的,数据源根据在IDE中注册的JDBC数据库连接指定。
    Java SE环境下,数据库可以通过数据源指定,也可以使用其他方法,取决于持久性提供者的要求。
  • 事务类型
    持久性单元指定事务是如何管理的。事务类型取决于目标容器,如果目标容器是Java EE容器,你可以使用容器管理或者应用管理。如果不是这样的话,你只能使用应用管理。
    • 容器管理的事务(JTA事务)

容器使用Java Transaction API来管理事务。你必须将你的应用部署在Java EE容器中,并且你的数据源必须支持JTA
persistence.xml中事务类型被设置为JTA.如果你使用Glassfish这个是默认选项。

    • 应用管理的事务(本地资源事务:Resource-Local Transaction

由应用负责事务处理。在persistence.xml文件中,被设置为RESOUCE_LOCAL

 

一个简单的persistence.xml配置文件例子: 

<?xml version="1.0" encoding="UTF-8"?>

<persistence version="1.0" xmlns="" xmlns:xsi="" xsi:schemaLocation=" /persistence_1_0.xsd">

<persistence-unit name="lite_db_pu" transaction-type="RESOURCE_LOCAL">

<provider>oracle.toplink.essentials.PersistenceProvider</provider>

 <class>com.e.liteworkflow.entity.Workflow</class>

 <class>com.e.liteworkflow.entity.Activity</class>

 <properties>

   <property name="toplink.jdbc.user" value="sa"/>

   <property name="toplink.jdbc.password" value="E2005"/>

   <property name="toplink.jdbc.url"

       value="jdbc:jtds:sqlserver://10.30.20.69/liteworkflowdb"/>

   <property name="toplink.jdbc.driver"

      value="net.sourceforge.jtds.jdbc.Driver"/>

   <!--property name="toplink.logging.level" value="FINE"/-->

   <property name="toplink.ddl-generation" value="create-tables"/>

   <property name="toplink.weaving" value="false"/>

 </properties>

</persistence-unit>

<persistence-unit name="lite_derby_pu" transaction-type="RESOURCE_LOCAL">

       <!— 省略了配置内容 -->

</persistence-unit>

</persistence>


对配置文件中一些重要元素和属性的解释:

    1. 配置文件中可以有多个<persistence-unit>persistence-unit>但是这些persistence-unitname属性不可以相同;
    2. persistence-unittransaction-type标明了事务由谁去管理,RESOURCE_LOCAL指本地数据库管理事务(不支持分布式事务),而JTA则是由托管的容器来管理事务(支持分布式事务);
    3. <provider>声明了类文件,这个类文件提供初始的factory方法来创建一个EntityManager实例;
    4. <class>class>列出应用程序中的实体类的名称(这里要有完整的包名和类名);
    5. <properties>properties>中的均是设置连接数据库的相关信息:

属性名:toplink.weaving

描述:是否加载实体使用织入,如果对@OneToOne@ManyToOne的关系实体使用惰性加载,则必须使用织入的方式。

可选值:true(默认):织入实体。

false:不织入实体。

static:静态织入实体,如果运行在不是Java EE 5容器中,或者JDK 不支持代理

   -javaagent:toplink-essentials-agent.jar方式运行时,需要设置此选项。

属性名:toplink.ddl-generation

描述:指定DDL生成方式,这些值定义在cle.toplink.essentials.ejb.cmp3.EntityManager FactoryProvider类中。

可选值:none(默认):不生成DDL,不自动生成数据库。

create-tables:只生成不存在的表,不改变原有表结构。

(create-tables属性值。如果表存在的话,代码执行结果就向存在的表格中插入记录,并且生成很多的警告信息,告诉你存在了那些表)

drop-and-create-tables:删除原表,重新生成新表。

 数据源的指定:根据自己的实现和应用服务器的默认行为,可能需要为jta-data-source(指定JTA数据源)/non-jta-data-source(指定非JTA数据源)设置提供值。


  • 实体对象以及注解介绍

具有ORM元数据的领域对象称为实体(Entity),按JPA的规范,实体具备以下的条件:

    • 必须使用javax.persistence.Entity注解或者在XML映射文件中有对应的元素;
    • 必须具有一个不带参的构造函数,类不能声明为final,方法和需要持久化的属性也不能声明为final
    • 如果游离状的实体对象需要以值的方式进行传递,如通Session bean的远程业务接口传递,则必须实现Serializable接口;
    • 需要持久化的属性,其访问修饰符不能是public,它们必须通过实体类方法进行访问。

 使用注解元数据

基本注解:

例子: 

@Entity(name = "T_TOPIC")

public class Topic implements Serializable ...{

@Id-1

@GeneratedValue(strategy = GenerationType.TABLE)-2

@Column(name = "TOPIC_ID")-3

private int topicId;

@Column(name = "TOPIC_TITLE", length = 100)

private String topicTitle;

@Column(name = "TOPIC_TIME") @Temporal(TemporalType.DATE)

private Date topicTime;

@Column(name = "TOPIC_VIEWS")

private int topicViews;

...

}

解释:

Entity标明该类(Topic)为一个实体类,它对应数据库中的表表名是T_TOPIC,这里也可以写成:

   @Entity

   @Table(name = "T_TOPIC")

其作用都是一样的

-1 Id标明该属性对应数据表中的主键

-2 GeneratedValue通过strategy属性指明主键生成策略,默认情况下,JPA自动选择一个最适合底层数据库的主键生成策略。在javax.persistence.GenerationType中定义了以下几种可供选择的策略:

1) IDENTITY:表自增键字段,Oracle不支持这种方式;

2) AUTO JPA自动选择合适的策略,是默认选项;

3) SEQUENCE:通过序列产生主键,通过@SequenceGenerator注解指定序列名,MySql不支持这种方式;

4) TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植。

-3 Column标明这个属性是数据表中的一列,该列的名字是TOPIC_ID

Column的一个属性length指明的是该属性的允许的长度。(个人认为设定该属性只是对于程序中操作该属性时增加了一验证过程,对数据库中该列原来的设置并没有影响,但是length属性指定的值必须不能大于数据库创建表时给该列限制的最大长度否则会出错)

Temporal(TemporalType.DATE):如果属性是时间类型,因为数据表对时间类型有更严格的划分,所以必须指定具体时间类型。在javax.persistence.TemporalType枚举中定义了3种时间类型:

1) DATE :等于java.sql.Date

2) TIME :等于java.sql.Time

3) TIMESTAMP :等于java.sql.Timestamp

 继承关系注解:

对继承关系进行注解,必须在父类中声明继承实体的映射策略。

例子:

@Entity(name = "T_TOPIC")

@Inheritance(strategy = InheritanceType.SINGLE_TABLE) ① @DiscriminatorColumn(name = "TOPIC_TYPE", discriminatorType =

DiscriminatorType.INTEGER, length = 1)

@DiscriminatorValue(value="1")

public class Topic implements Serializable ...{}

解释:

Inheritance通过strategy属性指明实体的继承策略。

javax.persistence.InheritanceType定义了3种映射策略:

1) SINGLE_TABLE:父子类都保存到同一个表中,通过字段值进行区分。

2) JOINED:父子类相同的部分保存在同一个表中,不同的部分分开存放,通过表连接获取完整数据;

3) TABLE_PER_CLASS:每一个类对应自己的表,一般不推荐采用这种方式。

DiscriminatorColumn如果继承策略采用第一种继承策略,则需要指明区分父子类的字段,DiscriminatorColumn就是用来指明区分字段的注解。

DiscriminatorValue 同样的采用第一种继承策略通过字段区分父子类,则用这个注解给该实体的区分字段赋值在这里赋的值为”1”.

 

关联关系注解:

例子:

@Entity @DiscriminatorValue(value="2")

public class PollTopic extends Topic ...{②继承于Topic实体

private boolean multiple;

@Column(name = "MAX_CHOICES")

private int maxChoices; @OneToMany(mappedBy="pollTopic",cascade=CascadeType.ALL)

private Set options = new HashSet();

//省略get/setter方法

}

解释:

通过@DiscriminatorValue将区分字段TOPIC_TYPE的值为2。由于PollTopic实体继承于Topic实体,其它的元数据信息直接从Topic获得。

OneToMany指定了一个一对多的关联关系,mappedBy属性指定“Many”方类引用“One”方类的属性名;cascade属性指明了级联方式(如果这里不指定为CascadeType.ALL的话,那么有关联关系的两个对象在做保存和删除操作时要分别来进行)建议:尽可能使用cascade=CascadeType.ALL来减少持久化操作的复杂性和代码量

注意JPA规范规定任何属性都默认映射到表中,所以虽然我们没有给③处的multiple属性提供注解信息,但JPA将按照 默认的规则对该字段进行映射:字段名和属性名相同,类型相同。如果我们不希望将某个属性持久化到数据表中,则可以通过@Transient注解显式指定:

@Transient

private boolean tempProp1;

@Entity(name="T_POLL_OPTION")

Public class PollOption implements Serializable ...{

@Id

@GeneratedValue(strategy = GenerationType.TABLE)

@Column(name = "OPTION_ID")

private int optionId;

@Column(name = "OPTION_ITEM")

private String optionItem;

@ManyToOne ①

@JoinColumn(name="TOPIC_ID", nullable=false)

private PollTopic pollTopic;

}

解释:

ManyToOne描述了多对一的关联关系,他是对该类引用的”One”(PollTopic)的属性(pollTopic)进行注解的。

JoinColumn指定关联”One”(PollTopic)实体所对应表的外键

 

 Lob字段的注解:
JPALob类型类型的持久化很简单,仅需要通过特殊的Lob注解就可以达到目的。

例子:

@Lob ①-1

@Basic(fetch = FetchType.EAGER)-2

@Column(name = "POST_TEXT", columnDefinition = "LONGTEXT NOT NULL")-3

private String postText;

@Lob

@Basic(fetch = FetchType. LAZY)-2

@Column(name = "POST_ATTACH", columnDefinition = "BLOB")-3

private byte[] postAttach;

解释:

-1 JPA 通过@Lob将属性标注为Lob类型

-2 通过@Basic指定Lob类型数据的获取策略,FetchType.EAGER表示非延迟 加载,而FetchType. LAZY表示延迟加载

-3 通过@ColumncolumnDefinition属性指定数据表对应的Lob字段类型。

 

使用XML元数据

 除了使用注解提供元数据信息外,JPA也允许我们通过XML提供元数据信息。按照JPA的规范,如果你提供了XML元数据描述信息,它将覆盖实体类中的注解元数据信息XML元数据信息以 orm.xml命名,放置在类路径的META-INF目录下。

<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings xmlns="/orm"

xmlns:xsi=""

xsi:schemaLocation="/orm /orm_1_0.xsd"

version="1.0">

①实体对象所在的包

<package>com.baobaotao.domain</package>

<entity class="Topic">

②Topic实体配置

<table name="T_TOPIC" />

<attributes>

<id name="topicId">

<column name="TOPIC_ID"/>

<generated-value strategy="TABLE" />

</id>

<basic name="topicTitle">

<column name="TOPIC_TITLE" length="30" />

</basic>

<basic name="topicTime">

<column name="TOPIC_TIME" />

<temporal>DATE</temporal>

</basic>

<basic name="topicViews">

<column name="TOPIC_VIEWS" />

</basic>

</attributes>

</entity>

<entity class="PollTopic">

②PollTopic实体配置

<discriminator-value>2</discriminator-value>

<attributes>

<basic name="maxChoices">

<column name="MAX_CHOICES" />

</basic>

<one-to-many name="options" mapped-by="pollTopic">

<cascade>

<cascade-all/>

</cascade>

</one-to-many>

</attributes>

</entity>

<entity class="PollOption">

②PollOption实体配置

<table name="T_POLL_OPTION" />

<attributes>

<id name="optionId">

<column name="OPTION_ID" />

<generated-value strategy="TABLE" />

</id>

<basic name="optionItem">

<column name="OPTION_ITEM"/>

</basic>

<many-to-one name="pollTopic" >

<join-column name="TOPIC_ID" nullable="false"/>

</many-to-one>

</attributes>

</entity>

<entity class="Post">

②Post实体配置

<table name="T_POST" />

<attributes>

<id name="postId">

<column name="POST_ID" />

<generated-value strategy="TABLE" />

</id>

<basic name="postText" fetch="EAGER">

<column name="POST_TEXT" column-definition="LONGTEXT NOT NULL"/>

<lob/>

</basic>

<basic name="postAttach" fetch="LAZY">

<column name="POST_ATTACH" column-definition="BLOB"/>

<lob/>

</basic>

</attributes>

</entity>

</entity-mappings>

使用这个orm.xml来描述实体信息的话,这里并没有标明两个继承类之间的关系,其继承信息将从实体类反射信息获取。

 

到这里我们的 实体 描述结束了,当然我们只是做了比较简单的描述,对于那些复杂的信息描述并没有进行讲述。实体描述结束了,有人会问如果我要来操作这些实体该怎么操作?这就是我们接下来要讲述的问题。 

  • EntityManager介绍 

实体对象由实体管理器进行管理,JPA使用javax.persistence.EntityManager代表实体管理器。实体管理器和持久化上下文关联,持久化上下文是一系列实体的管理环境,我们通过EntityManager和持久化上下文进行交互。 

有两种类型的实体管理器:

    • 容器型:容器型的实体管理器由容器负责实体管理器之间的协作,在一个JTA事务中,一个实体管理器的持久化上下文的状态会自动广播到所有使用EntityManager的应用程序组件中。Java EE应用服务器提供的就是管理型的实体管理器;
    • 应用程序型:实体管理器的生命周期由应用程序控制,应用程序通过javax.persistence.EntityManagerFactorycreateEntityManager创建EntityManager实例。 

EntityManager的创建过程图:

我们在程序中的创建EntityManager的代码:

EntityManagerFactory currentManagerFactory = Persistence.createEntityManagerFactory(persistenceUtilName);

EntityManager em = currentManagerFactory.createEntityManager();

实体的状态  

实体对象拥有以下4个状态,这些状态通过调用EntityManager接口方法发生迁移:

1) 新建态:新创建的实体对象,尚未拥有持久化主键,没有和一个持久化上下文关联起来。

2) 受控态:已经拥有持久化主键并和持久化上下文建立了联系;

3) 游离态:拥有持久化主键,但尚未和持久化上下文建立联系;

4) 删除态:拥有持久化主键,已经和持久化上下文建立联系,但已经被安排从数据库中删除。 

通过EntityManager中的接口方法可以改变实体对象的状态:

a) void persist(Object entity)

通过调用EntityManagerpersist()方法,新实体实例将转换为受控状态。这意谓着当persist ()方法所在的事务提交时,实体的数据将保存到数据库中。如果实体已经被持久化,那么调用persist()操作不会发生任何事情。如果对一个已经删除的 实体调用persist()操作,删除态的实体又转变为受控态。如果对游离状的实体执行persist()操作,将抛出 IllegalArgumentException

在一个实体上调用persist()操作,将广播到和实体关联的实体上,执行相应的级联持久化操作;

 

b) void remove(Object entity)

通过调用remove()方法删除一个受控的实体。如果实体声明为级联删除(cascade=REMOVE 或者cascade=ALL ),被关联的实体也会被删除。在一个新建状态的实体上调用remove()操作,将被忽略。如果在游离实体上调用remove()操作,将抛出 IllegalArgumentException,相关的事务将回滚。如果在已经删除的实体上执行remove()操作,也会被忽略; 

 

c) void flush()

将受控态的实体数据同步到数据库中; 

d) T merge(T entity)

将一个游离态的实体持久化到数据库中,并转换为受控态的实体; 

e) T find(Class entityClass, Object primaryKey)

以主键查询实体对象,entityClass是实体的类,primaryKey是主键值; 

f) Query

JPA使用javax.persistence.Query接口代表一个查询实例,Query实例由EntityManager通过指定查询语句构建。该接口拥有众多执行数据查询的接口方法:

Object getSingleResult():执行SELECT查询语句,并返回一个结果;

List getResultList() :执行SELECT查询语句,并返回多个结果;

Query setParameter(int position, Object value):通过参数位置号绑定查询语句中的参数,如果查询语句使用了命令参数,则可以使用Query setParameter(String name, Object value)方法绑定命名参数;

Query setMaxResults(int maxResult):设置返回的最大结果数;

int executeUpdate():如果查询语句是新增、删除或更改的语句,通过该方法执行更新操作;

还有就是关于JPA的查询语言,JPA的查询语言是面向对象而非面向数据库的,它以面向对象的自然语法构造查询语句,其查询语言类似于HQL语句,在这里就不再赘述。

阅读(951) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~