Java持久化API(JPA)开发中如何使用实体

 无论你的应用程序是桌面模式还是使用应用程序服务器如GlassFish,Java持久化API需要你唯一标识你的类,这些类将会存储在数据库中。 该Java持久化API使用关键字术语entity来定义类,这些定义的类后来将会被映射到一个关系数据库中。
你需要标识实体和使用声明来定义这些实体之间的关系。Java编译器会识别它们,并根据声明来保存的当前的工作。使用声明,编译器会自动为你生成辅助类,完成编译错误检查。

一、实体声明
 
或许最为重要的声明就是 javax.persistence.Entity。 该声明标识了实体类,你使用的所有的含有持久化API的类的定义都需要这个声明。实体类在关系数据库中称为表。实体对象将会映射在一张表或者很多表中映射成许多的行。
下面的事例代码开始定义了一个棒球运动员 Player类。声明在代码中以符号@开始。
@Entity
public class Player {

注意到实体声明在类定义之前。Java持久化API实现将会为 Player实体在关系数据库中创建一张表格。默认的表格名和类名是不一致的,这个例子中,名为 PLAYER 的表格将会代替 Player实体。
对于实体的约束很少但是都很重要。首先,实体必须是顶层的类。你不能从枚举类型或者接口下创建实体。另外,你所写的类不能是 final类,或者 final方法,或者 final常量。
除了这几个限制外,实体可以使用Java语言中大多数的特点。举个例子,实体可以是抽类或者具体类。然而,实体类必须要由其它实体子类实现,这些实体子类将会用于数据库存贮。这些类可以被其它的实体或者非实体类继承,重写,扩展。
二、字段和属性
 
任何一个实体的状态都是由它的字段值或者属性值所定义的。
当进行检索或者存储的实体的时候,你能决定 Java持久化AP是否使用你的变量字段或者 getters 和 setters 方法。如果你声明了那些事例变量,持久化方案提供工具(persistence provider)将直接访问事例变量。如果你声明为 JavaBean风格的 getter 和setter方法,持久化方案提供工具将会在加载或者存储这些持久化状态时使用getter 和setter方法。你应该选择其中一种风格,目前的规范中,混合使用者这两种风格是不合法的。目前的持久化规范给我们演示了一些例子,这些例子中使用的是getters 和 setters 方法,因此这篇文章将沿用这个约定。
你可以使用大多数的基本类型作为你的持久化字段,包括基本类型,基本类型包装,字符串和许多其它的类型。你应该查一下该API规范,看看它所允许的字段类型。
所有的实体必须要有一个主键。主键是单独唯一的字段或者多字段组合标识。本文使用的单个字段主键,当然更具需要,你可以将多个字段作为你的主键。标识单个字段主键用 Id声明。
主键字段必须是下面类型中的一种:
     基本类型(如 int、long等)
     基本类型的包装(如nteger、 Long等)
     java.lang.String
java.util.Date
java.sql.Date
下面的代码事例定义了 Player类。这个例子演示了你如何使用四种不同的声明:Entity,Id, GeneratedValue 和Transient。
@Entity
public class Player implements Serializable {
private Long    id;
private String  lastName;
private String  firstName;
private int     jerseyNumber;
private String  lastSpokenWords;
/** Creates a new instance of Player */
    public Player() {
}
/**
* Gets the id of this Player. The persistence provider should
* autogenerate a unique id for new player objects.
* @return the id
*/
    @Id
@GeneratedValue
public Long getId() {
return this.id;
}
/**
* Sets the id of this Player to the specified value.
* @param id the new id
*/
    public void setId(Long id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String name) {
lastName = name;
}
// ...
// some code excluded for brevity
// ...

/**
* Returns the last words spoken by this player.
* We don't want to persist that!
*/
    @Transient
public String getLastSpokenWords() {
return lastSpokenWords;
}
public void setLastSpokenWords(String lastWords) {
lastSpokenWords = lastWords;
}
// ...
// some code excluded for brevity
// ...
}

实体的主键是 id属性,并且该属性正确的被 Id声明。主键得值可以自动生成。自动生成id的行为还没有完全被指定,但是如果你增加对主键的 GeneratedValue声明的话,就会自动产生一个键。

@Id
@GeneratedValue
public Long getId() {

通常是明确的标识属性和字段,而不应该把它们设置为持久化的。使用声明 Transient来标识过渡( transient)属性。你也可以使用语言关键字 transient来标识字段。属性和字段被 Transient所声明后,将不会是在数据库中持久化存储。上面的 Player代码事例中,你会注意到 lastSpokenWords属性使用了Transient声明。字段被Java语言关键字所标识的话,将不会被串行化或者持久化。字段或者属性以 Transient标识将不会在数据库中持久化。
三、实体之间的关系
就像现实世界中一样,你的持久化对象不是单独存在的。实体类通常会和其它的类交互,要么为它的类提供服务或者使用其它类的服务。类和其它的类可以是一对一的,一对多的,多对一的,或者多对多的关系。
你能在本文的棒球运动员和棒球队例子中,找到这些关系。
举个例子,一个 Player又有一个击球率。击球率和该运动员就是一对一的关系。一个 Player有一个击球率。这个击球率只属于某一个 Player。
一个Team有许多的 Player。虽然个别的 Player只属于一个单独的Team,一个Team包括许多的 Player。Team类和Player类之间的俄关系就是一对多的关系。一个Team有许多的 Player。另外一个类似的例子就是多对一的关系。多对一的关系通常是将一对多的关系反过来看。举个例子,如果你你从一个 Player的角度考虑他和Team之间的关系,我们很清楚的说 Player 和 Team是多对一的关系。
 

图一:多个球员对一个球队的关系
一个Team在每一个赛季打了很多的比赛,同时每个比赛有很多的Team来参加。从关系数据库的角度来说,Team和比赛就是多对多的关系。
通过持久化API,你可以模拟这些实体的关系。如果每次你的实体对象有这些关系的时候,你就可以在相关的实体中应用其中的一种声明:
1. OneToOne
2.  OneToMany
3.  ManyToOne
       4.  ManyToMany
数据库关系可以是单方向的,意思说只有一个实体知道与其它实体之间的关系。单向的关系就存在"主"方,"主"方保留数据库中的关系信息。
双向关系同时有"主"方和"反"方。"主"方决定如何,什么时候来更新实体间的关系。同时,"主"方中也包含了相对其它实体来说的外键。
回到棒球的例子, Player对象和Team对象有多对一的关系。虽然现在显示的世界中来说不合理,但是在数据库中, Player确是关系的决定者。在 Player类中将 team属性增加为 ManyToOne声明,就完成了 Player 和 Team实体间多对一的关系,代码如下:
@Entity
public class Player implements Serializable {
...
private Team team;
@ManyToOne
public Team getTeam() {
return team;
}
...
}

既然 Team 和 Player 类存在一个双向的关系,那么你必须也要在"反"方定义这个关系,与"正"方相对应。从 Team这个类的角度出发,它们之间的关系是一对多的。另外, Player实例通过 其team变量来访问 Team实例。在一对多的关系中使用 mappedBy映射属性,持久化引擎会知道如何匹配players和 teams。在双向关系中, mappedBy映射属性存在于"反"方,即使Team实体。在这个例子中, mappedBy映射属性显示了一个 Player事例的 team属性映射到一个 Team事例。映射 Player对象的 team属性就是意味着 Team对象的标识号将在 PLAYER表中作为一个外键,单独成为一列。
Team实体类将会立即在下面显示。注意getPlayers方法,被 OneToMany声明,并且是 mappedBy属性。
@Entity
public class Team implements Serializable {
private Long id;
private String teamName;
private String division;
private Collection<Player> players;
/** Creates a new instance of Team */
    public Team() {
players = new HashSet();
}
/**
* Gets the id of this Team.
* @return the id
*/
    @Id
@GeneratedValue
public Long getId() {
return this.id;
}
/**
* Sets the id of this Team to the specified value.
* @param id the new id
*/
    public void setId(Long id) {
this.id = id;
}
@OneToMany(mappedBy = "team")
public Collection<Player> getPlayers() {
return players;
}
public void setPlayers(Collection<Player> players) {
this.players = players;
}
...
}

四、表和缺省列名
 
Java持久化API规范提供了有用的缺省声明属性。你可以阅读规范获得更多的详情,我只在这里提供一些例子。
每个实体都有一个名字。实体名就是实体的类名的所有大写。实体声明有一个 name 属性,这个属性能够让你请清楚楚的指定实体名称。在以后的查询中,你会用到这个实体名称的。在 Player的例子中,在以后的查询中,你可以使用 Player实体的名字。如果你想将实体命名为 BaseballPlayer,使用 name属性就可以进行指定了,代码如下:
@Entity (name="BaseballPlayer")
public class Player {

同时 BASEBALLPLAYER也就称为这个实体的表名了,但是你在 Table声明可以改变表名 。 Table有三个可修改的属性,其中一个是决定实体的表名。在 Table声明中使用 name属性,将表名改为 BASEBALL_PLAYER,代码如下:
@Entity
@Table(name="BASEBALL_PLAYER")
public class Player {

在默认的情况下,在实体表使用实体的字段或者属性名作为列名。举个例子,既然 Player类中有一个 lastName属性,与之相应的列名就是 LASTNAME了。这是缺省设置没有什么值得惊奇的,这些列名和你在应用程序代码中使用的名字是一样的。另外,你可以使用 Column声明和该声明的 name标签来修改列名。如果你想把 LASTNAME换成 SURNAME,你可以声明 lastName属性成这个样子:
@Column(name="SURNAME")
public String getLastName() {

有许多的声明和可供选择的标签存在。它们有助于你控制列的长度和列数据的大小,唯一性的需求,级联操作,和其它的关系数据库中的标准选项等等。本文只是提供一些信息和几个少数常用的声明的例子,能够让你尽快入门该API.
五、持久化单元
你应用程序的实体集称为持久化单元。你可以在名为 persistence.xml的文件中配置定义你应用程序的持久化单元。这个文件存在于你的 META-INF目录下。你可以将 META-INF 子目录放在你的项目源文件下。本文事例的持续项目,你可以下载,你就会发现源文件的目录结构和图2很相似:
 


图二:源文件目录结构
persistence.xml有许多的功能,但是在开发桌面应用程序的时候,大多数重要的任务是列出你应用程序中所有的实体和对持久化单元命名。列出持久化实体类需要在可移植的 Java SE环境。为本文的实体所写的简单的 persistence.xml会是这样的:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="
http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="league" transaction-type="RESOURCE_LOCAL">
    <provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
    <class>com.sun.demo.jpa.Player</class>
    <class>com.sun.demo.jpa.Team</class>
    <properties>
      <property name="toplink.jdbc.user" value="league"/>
      <property name="toplink.jdbc.password" value="league"/>
      <property name="toplink.jdbc.url" value="jdbc:derby://localhost:1527/league"/>
      <property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
      <property name="toplink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

这个文件只是一个大致的模板,像 NetBeans IDE 5.5这样的工具会给你创建一个正确的框架。本位用到的最为重要的标签如下:

1.  persistence-unit
2.  provider
3.  class
4.  property
持续单元的名称可以是任何你所选择的名称。名称并不一定需要是你数据库的名称或者模型的名称,但是取一致的名称很有帮助的。在这个例子中,持久化单元的名称是 league。当你在 Java SE应用程序中使用此API时,缺省的事务类型是 RESOURCE-LOCAL。 在 Java EE的环境下,你将会看到 JTA事务类型,我的意思就是说实体管理器参与到事务处理之中。如果你不指定类型的话,就会使用缺省的设置,这将很容易使你的应用程序环境发生错误,从一个环境变到了另一个环境。
Provider标签声明了类文件,这个类文件提供初始的 factory方法,来创建一个 EntityManager实例。你的持久化API 实现程序将会提供一些详情,使你获得此标签的正确值。 persistence.xml文件例子展示了 provider类名。
使用 class标签列出你应用程序中的实体类的名称。在本文的举例代码中,就只用到两个实体: com.sun.demo.jpa.Player 和 com.sun.demo.jpa.Team. 完整的包名和类名是必须的。持久化方案提供工具 (persistence provider)通过查阅 persistence.xml 文件中的实体名称,就是确定将应用程序的哪些类映射到关系数据库中去 。
最后,在 Java SE桌面环境,如果Java 命名目录接口( JNDI)查找不能用的话,你应该将数据库的连接信息写到 persistence.xml文件中去。数据库连接属性包括数据库连接的用户名和密码,数据库连接字符串,驱动名称。另外甚至包括在持久化方案提供工具 (persistence provider)属性选项上创建或者撤销新表。属性名称 javax.persistence是保留的属性标签,是规范所定义的,不可改动的。提供商使用他们自己的命名空间,以免和规范发生冲突。先前展示的 persistence.xml 文件中,使用的是 GlassFish实现。它的提供商所制定的属性在 toplink命名空间中,它不是规范本省的一部分。持久化方案提供工具 (persistence provider)将会忽略不在规范里面的所有属性或者不是自己指定供应商的属性。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值