使用 Java Persistence API 运行数据
很多 web 应用程序是以数据为中心 — 它们显示数据并允许用户新建或更新数据。这听上去很简单,但真到了一些基本操作,如在数据库中读写数据,情况却非常糟糕。尽管如此,Java Persistence API (JPA) 却极大地减少了必须编写的冗长的样板式代码。我们将看一个使用 JPA 的简单例子。
本文中,将开发一个简单的管理青年足球联赛的应用程序。开始时将建立一个简单的数据模型,用于跟踪球队及其队员。将使用 JPA 完全访问这些数据。以第一个数据模型 Team
开始。清单 1 显示了此类。
清单 1.
Team
数据模型类
@Entity public class Team { .... ....@Id ....@GeneratedValue(strategy = GenerationType.IDENTITY) ....private long id; .... ....private String name; .... ....@OneToMany ....private Collection |
这是典型的 JPA 注释类。使用 @Entity
注释来声明该类将映射到数据库。可以选择指定类的表名,或者在使用与类相同的名称时实现约定。其次,您要注释该类的 id
字段。想要它成为表的主键,使用 @Id
注释来声明。从逻辑角度来看,id
并不重要;只将其用于数据库。因为想要数据库充分发挥其价值,使用 @GeneratedValue
注释。
在 清单 1 中,还要声明另一个字段,name
字段。它是球队的名字。请注意该字段没有 JPA 注释。默认情况下,这会映射到同名列,这已符合本文意图。最后,每个球队都有多名队员与之关联。使用 @OneToMany
注释让 JPA 运行时知道这是一个管理关系,球队有多名球员。在 Java 类中,这是 Player
对象的 java.util.Collection
。清单 2 显示 Player
类被引用。
清单 2.
Player
数据模型类
@Entity public class Player { .... ....@Id ....@GeneratedValue(strategy = GenerationType.IDENTITY) ....private long id; .... ....private String firstName; .... ....private String lastName; .... ....private int age; .... ....@ManyToOne (cascade=CascadeType.ALL) ....private Team team; .... // getters and setters } |
清单 2 中显示的 Player
类与 清单 1 中的 Team
类类似。它的字段更多,但大多数情况下不必担心注释这些字段。JPA 将帮助您正确完成。清单 1 和 清单 2 的一个不同之处是如何指定 Player
类与 Team
类的关系。本例中使用 @ManyToOne
注释,因为一个 Team
中有多个 Players
。请注意,还指定了一个级联策略(cascade policy)。参考 JPA 文档,选择适用于您的本应用程序的级联策略。本例中,使用该级联策略可以同时创建新的 Team
和 Player
,JPA 将保存两者,这将方便应用程序开发。
既然已经声明两个类,只要告诉 JPA 运行时如何连接数据库。通过创建 persistence.xml 文件来完成。JPA 运行时需要找到该文件,并使用其中的元数据。最简单的方法是放入 /META-INF 目录,这是源代码的子目录(需要将它放到输出编译类的根目录中)。清单 3显示 persistence.xml 文件。
清单 3. 足球应用程序的 persistence.xml
|
回头看看 清单 1 和 清单 2,所有代码都是通用的 JPA 代码。实际上曾使用的都是 JPA 注释和它的常量。对已使用的数据库和 JPA 实现没有什么特别的。正如在 清单 3 中看到的,persistence.xml 文件正是存放这些特定内容的地方。有一些出色的 JPA 实现可用,包括 OpenJPA 和 TopLink(见 参考资料)。已经使用过古老的 Hibernate,因此有几个 Hibernate 相关属性已经指定。这些多数都是简单易懂的,如 JDBC 驱动器和 URL,而且是很有用的,如通知 Hibernate 登陆到正在执行的 SQL (一些您肯定不想放在产品环境中的内容,但对于调试却很重要)。
在 清单 3 中,您还会注意到使用了 Apache Derby 数据库。实际上,使用的是数据库的嵌入式版本。您不必单独启动数据库,且担心配置。此外,在连接 URL 中您也指出应该自动创建数据库,并且您也通知 Hibernate 自动创建模式(这是 hibernate.hbm2ddl.auto
属性)。因此如果只运行应用程序,可以创建数据库及表。这有益与开发,但当然您会希望产品系统设置不一样。现在数据模型代码已经生成,并可通过 JPA 访问,我们将查看开放该数据,以便应用程序能使用它。
回页首
使用 JAX-RS 对数据的 RESTful 访问
如果是五年前创建该应用程序,现在可能会使用一些 Java Server Pages (JSPs) 或 Java Server Faces (JSFs) 或一些其他模板技术。不需要服务器上为该应用程序创建 UI,而是使用 Dojo 在客户端创建。所需要做的就是让客户端代码使用 Ajax 来访问数据。也可使用模板解决方案来解决这类问题,但使用 Java API for RESTful Web Services (JAX-RS) 要简单得多。一开始我们创建一个类,用于读取数据库中所有 Teams
以及创建新的 Teams
。清单 4 显示该类。
清单 4.
Teams
的数据访问类
@Path("/teams") public class TeamDao { .... ....private EntityManager mgr = DaoHelper.getInstance().getEntityManager(); .... ....@GET ....@Produces("application/json").... ....public Collection |
清单 4 显示的是类数据访问对象类,名为 TeamDao
。随后我们将注释这个类,但首先我们讲解一下数据访问。该类引用 JPA 类EntityManager
。这是 JPA 的中心类,并提供对底层数据库的访问。作为检索联赛中所有球队的首选方法,使用 EntityManager
来创建查询。查询使用 JPA 的查询语言,它与 SQL 相似。该查询获取所有 Teams
。对于第二个方法,只要用传入的球队名创建新的Team
,用 EntityManager
创建事务、保存新球队并执行事务。所有代码都是普通的 JPA 代码,因为所有类和接口都是基础 API 的一部分。
现在已经理解了 清单 4 中 JPA 部分,我们再探讨其中的 JAX-RS 部分。您首先会注意到的是使用 @Path
注释来向基于 HTTP 的客户端公开此类。/teams
字符串指定该类的相对路径。完整的 URL 路径是
。/SoccerOrg
将会指定您的 web 应用程序的路径(当然,您可以配置成不同的,也可完全移除)。/resources
部分将用于指定 JAX-RS 终端。/teams
对应@Path
注释并指定使用哪些 JAX-RS 类。
接下来,第一个方法 getAll
有一个相关的 @GET
注释。它指定如果接收到 HTTP GET
请求,则调用该方法。接着,该方法有个@Produces
注释。它声明响应的 MIME 类型。本例中,您要生成 JSON,因为它是基于 JavaScript 的客户端最易使用的。
这就是要使用 JAX-RS 将类公开给客户端所要做的所有事情。尽管如此,您可能会问自己:如果该方法返回 Team
对象的java.util.Collection
,又如何发送给 web 客户端?@Produces
注释声明您想以 JSON 方式发送,但 JAX-RS 如何将其序列化成 JSON?其实要完成这步您只需在 Team
类中多加一个注释,如 清单 5 中所示。
清单 5. 修改过的
Team
类
@XmlRootElement @Entity public class Team { .... // unchanged from Listing 1 ........ } |
通过添加 @XmlRootElement
注释,JAX-RS 现在可以将这个类转换成 JSON 对象。您也许记得这个类。它不是 JAX-RS 的一部分;而是 Java Architecture for XML Binding (JAXB) API 的一部分,而这又是核心 Java 1.6 平台的核心部分。该注释看上去像指出它用于 XML,但实际上可用于各种 JAXB 输出,包括 JSON。还有很多其他 JAXB 注释,但您在本例中只要用到这个。它只使用常用方法将Team
类所有字段序列化成 JSON。
现在回到 清单 4 看看类的第二个方法,createTeam
方法。该方法使用 @POST
注释来指定当接收到 HTTP POST
请求时调用该方法。下一步,它用 @Consumes
注释来声明它能使用 哪种 POST
请求。此处指定的值对应于内容类型头部的 HTTP 请求。本例中,它指定为 x-www-form-urlencoded
。它是提交 HTML 表单时将会接收到的类型。因此,该方法会在 /SoccerOrg/resources/teams 终端提交 HTML 表单时调用。最后,请注意该方法只用一个输入参数,一个名为 teamName
的字符串。请注意该参数用 @FormParam
注释声明。它告诉 JAX-RS 运行时在请求主体内查找一个名为 teamName
的表单参数(注释值)并将其绑定到传入方法注释的变量中。有了这些,就可以轻松处理表单提交并且将其嵌入到代码中。如果提交很多数据,可能会导致混乱。本例中,您也许想使用更结构化的方法。清单 6 显示的是创建 Player
对象的简单例子。
清单 6. 使用 JAX-RS 处理结构化的
POST
数据
@Path("/players") public class PlayerDao { ....private EntityManager mgr = DaoHelper.getInstance().getEntityManager(); .... ....@POST ....@Consumes("application/json") ....@Produces("application/json") ....public Player addPlayer(JAXBElement |
清单 6 中的 PlayerDao
类与 清单 5 中的 TeamDao
类很像。需要检查的主要要差异是它的 addPlayer
方法。该方法处理 HTTP POST
请求,类似于 TeamDao
中的 createTeam
方法。然而,它使用 application/json — 也就是说它希望获得的是 JSON 数据。这有两个含义,首先,请求需要指定 application/json 的内容类型,从而可以调用该方法。其次,post 的主体应该是 JSON 数据。请注意该方法的输入参数是 JAXBElement
类型,也就是说它是 Player
对象的 JAXB 包装。它告诉 JAX-RS 自动将传送的数据解析成JAXBElement
包装,所以您不必编写任何解析代码。请注意,在方法的主体中,只用一行代码获取完整的 Player
对象,然后它可使用 JPA 将新的 Player
保存到数据库。
要完成 JAX-RS 需要做的最后一件事是显示用于包装所有内容的配置。为此,只需修改应用程序的 web.xml 文件。清单 7 显示的是应用程序的 web.xml。
清单 7. 应用程序的 web.xml
|
如您在 清单 7 中所见,应用程序中只有一个 servlet 声明。该 servlet 是由 Jersey 所提供的,这是使用的 JAX-RS 实现。传递一个初始化参数给 servlet — 包中含有您希望 JAX-RS 了解的所有类。本例中,有一个存放数据模型的包和一个存放数据访问对象的包。您希望能找到这些模型,以便 JAX-RS 能够将其转换为 JSON。当然,需要找到 DAO 以便 JAX-RS 能将请求路由到它们。最后,请注意 servlet 映射。这里指定了您的 URL 路径的 /resources 部分。现在已经准备好在客户端使用所有后台代码创建一个使用 Dojo 的 UI。
回页首
利用客户端的 REST 和 Dojo
Dojo 工具箱提供了几乎所有构造 web 应用程序客户端所需的库或工具。您会看到它在使用 Ajax、表单、JSON 和创建 UI 小部件时如何提供帮助。(它可以做的更多,而这恰好是在该简单例子中您所需要的。)它是如此庞大的一个系统,您可能想要下载完整的工具箱并根据您应用程序所需自定义构造。对于本应用程序示例,您可以换用 Google Ajax API 来访问您所需要的工具箱的各个部分。这很方便,而且具有性能优势,因为 Google 的 Dojo 版本是通过 Google 自己的高效内容发布网站(CDN)提供的。
您的应用程序是以数据为中心的,因此开始时可以加入一些数据。我们将使用 Dojo 创建 UI 来添加 Teams
。清单 8 显示所有您所需要的代码。
清单 8. 使用 Dojo 添加
Teams
.... |
注意在 清单 8 中,您引用了来自 Google CDN 的基础 Dojo 库。完成之后,您就可以使用 dojo.require
函数请求 Dojo 额外部分(见 清单 8 中代码底部)。请注意您刚创建了一个普通 HTML 表单,但使用了一些额外的 Dojo 相关属性。这告诉 Dojo 在可视化元素中加入额外的样式,并在对应的 DOM 元素中加入额外功能。告诉 Dojo 一旦其他东西加载(所有 Dojo 组件)就执行 init
函数。此函数中,使用 dijit.byId
函数取得表单中按钮的事件处理函数。Dijit
是 Dojo 的小部件库。可以使用 dojo.byId
来根据 ID 引用任何 DOM 元素,但类似的 dijit.byId
会赋给小部件额外的功能(如果元素标记为 widget,则如 清单 8 中按钮所示)。
然后可以使用 Dojo 为按钮点击关联一个事件处理函数。事件处理函数停止表单提交,并使用在 dojo.xhrPost
函数中使用 Ajax。该函数使 POST
HTML 表单很容易。它通过检测 HTML 表单的 action
属性区分 Ajax 客户端。它还读取所有的表单元素并将它们传递到 Ajax POST
。当它接收到服务器返回的响应,它就调用传递给 xhrPost
的 load
函数。请注意,通过设置传递给 xhrPost
函数的handleAs
属性来返回 JSON。很快将看到 addTeam
函数,但可以直接传入数据对象,因为已将 JSON 数据安全解析成可用的 JavaScript 对象。该 addTeam
函数将与另一表单联合使用,来添加 Players
。清单 9 显示的是表单的 HTML。
清单 9. 添加
Player
表单
Add a Player |
该表单,与 清单 8 中的一样,是一个有效的 HTML 表单。尽管如此,元素中却添加了 Dojo 特定属性。请注意,有一个 SELECT
元素,它将作为 Teams
的下拉列表,让用户可选择将 Player
加入哪个 Team
。这是需要从服务器加载的动态数据。请注意在启动时还加入了另一个函数 — loadTeams
函数。它用于从服务器加载球队。清单 10 显示了该函数,以及 addTeam
函数,如您所见,它在 清单 9曾被引用。
清单 10.
loadTeams
和 addTeam
函数
var teams = {}; function loadTeams(){ ....var select = dijit.byId("team"); ....dojo.xhrGet({ ........url: "/SoccerOrg/resources/teams", ........handleAs:"json", ........load : function(data){ ............var i = 0; ............for (i in data.team){ ................addTeam(data.team[i]); ............} ........}, ........error : function(error){ ............alert("Error loading team data: " + error); ........} ....}); } function addTeam(team){ ....teams[team.id] = team; ....var select = dijit.byId("team"); ....var opt = {"label":team.name, "value":team.id}; ....select.addOption(opt); } |
这里,再次用到了 Dojo 的 Ajax 工具访问数据,此工具由先前创建的 JAX-RS 终端所提供。这次使用的是 dojo.xhrGet
,它向 Ajax 终端发出 HTTP GET
请求。在此例中,需要指定它的 URL,否则它与 清单 9 中所看到的 xhrPost
一样。最后,是 addTeam
方法。这里再次使用 Dojo 小部件的额外功能向显示球队的下拉列表轻松添加新选项。现在已经看到球员表单是如何创建的,再看看处理提交的代码(见 清单 11)。
清单 11. 添加新
Player
var button = dijit.byId("addPlayerBtn"); dojo.connect(button, "onClick", function(event){ .... event.preventDefault(); event.stopPropagation(); var data = dojo.formToObject("addPlayerForm"); var team = teams[data.team]; data.team = team; data = dojo.toJson(data); var xhrArgs = { postData: data, handleAs: "json", load: function(data) { alert("Player added: " + data); dojo.byId("gridContainer").innerHTML = ""; loadPlayers(); }, error: function(error) { alert("Error! " + error); }, url: "/SoccerOrg/resources/players", headers: { "Content-Type": "application/json"} }; var deferred = dojo.xhrPost(xhrArgs); }); |
这段代码将向 PlayerDao.addPlayer
方法提交数据,该方法您已在前面的 清单 6 中见过。这段代码预计将 Player
对象序列化成 JSON 数据结构。首先,您再次使用 Dojo 来包装表单上按钮点击处理函数。下一步,使用 Dojo 的函数 dojo.formToObject
来将表单中数据转换成 JavaScript 对象。然后稍微修改 JavaScript 对象,以匹配服务器能接收的结构。然后使用 Dojo 的 dojo.toJson
函数将它转换成 JSON 字符串。现在它传给 dojo.xhrPost
,与 addTeam
表单提交方式一样。请注意,您添加了 HTTP 头部 Content-Type
来保证它路由到 PlayerDao.addPlayer
方法。
xhrPost
也有一个 load
函数,它会在 Ajax 请求从服务器携带成功响应返回后调用。本例中,它会清空页面上名为 gridContainer
的元素,并调用名为 gridContainer
的元素,loadPlayers
的函数。这是另一个用来显示所有球员的 Dojo 小部件。清单 12 显示用于此目的的 HTML 和 JavaScript 。
清单 12. Player grid HTML 和 JavaScript
|
清单 12 显示了 Dojo 的 DataGrid
小部件。它是 Dojo 中一个更丰富的小部件,因此也需要额外的 CSS。要新建一个 grid,要做两件事。首先,需要创建数据源。本例中,是来自服务器的 JSON 数据,因此创建一个新的 JsonRestStore
对象,并将它指向将会产生数据的服务器 URL。然后覆盖 _processResults
。只需要这样做,因为它能预计接收 JSON 数组的数据,并且 JAX-RS 终端将会生成更复杂一点的对象(它有一个属性,名为 player
,它的值是 JsonRestStore
预计接收的 JSON 数组)。grid 还需要布局元数据,以描述显示哪些列,以及 JavaScript 对象对应的属性是什么。然后就可以创建 grid 并将它放入 DOM 目录树中。
现在已完成足球应用程序示例,有很多方法显示联赛中的球员。您可以轻松扩展此处的简单示例,如添加球员编辑功能,对 grid 排序,甚至可以加入更多的数据,如比赛和结果。
回页首
结束语
本文演示了快速创建丰富的、以数据为中心的 web 应用程序的方法。使用了几项关键技术移除服务器端和客户端的繁杂的样板式代码:JPA、JAX-RS 和 Dojo。很多情况下,可以直接使用默认的惯例设置进一步减少 web 应用程序中的代码量。其结果是使用最少的代码创建非常先进的 web 应用程序。使用到的所有技术都是可扩展和具有量产品质的,因此您可放心扩展此应用程序示例(或您自己的应用程序),直接用于更加健壮的用例。更棒的是没有壁垒。使用的是服务器端的开放标准。例如,可以轻松转换数据库技术。在前台使用 REST 和 JSON,这意味着可以使用不同的 UI 套件,或者可以轻松连接上移动客户端。
回页首
下载
文章源代码 | SoccerOrg.zip | 14KB | HTTP |
关于下载方法的信息
参考资料
学习
- Spring 2 和 JPA 简介(Sing Li,developerWorks,2006 年 8 月):了解更多关于 JPA 的内容。
- 使用 JPA 和 实现组合键(Stephen Morris,developerWorks,2009 年 8 月):进一步深入了解 JPA 和 Hibernate。
- 用 Java 技术创建 RESTful Web 服务(Dustin Amrhein 与 Nick Gallardo,developerWorks,2010 年 2 月):详细介绍 JAX-RS。
- 为移动 web 创建 Ajax 应用程序(Michael Galpin,developerWorks,2010 年 3 月):查看 JAX-RS 如何能很好地用于移动 web 应用程序开发。
- 编写一个客户端 Dojo 应用程序(Wendi Nusbickel 和 Melissa Betancourt,developerWorks,2008 年 12 月):找到更多关于 Dojo 的内容。
- 使用 Dojo 开发 HTML 小部件(Igor Kusakov,developerWorks,2006 年 10 月):探索 Dojo 的扩展性。
- “评论专栏:使用 Ant 和 ShrinkSafe 提高 Web 性能”(Kevin Haverlock,developerWorks,2010 年 3 月):查看在使用 Dojo 的实际应用程序中,如何使用 REST 调用轻松加载 JSON 数据来填充 Dojo Dijit 树小部件。
- “评论专栏: 散漫加载您的 Dojo Dijit 树小部件可以提高性能”(Scott Johnson,developerWorks,2008 年 5 月):学习如何使用 Ant 构造工具和 Dojo 的 ShrinkSafe 自动化配置文件构造,从而提升基于 Dojo 的 web 页面。
- developerWorks Web 开发专区 专门提供包含各种基于 web 的解决方案的文章。
获得产品和技术
- 下载 Dojo toolkit。
- 获取 Java SDK。本文中用使用的是 JDK 1.6.0_17。
- 获取 Apache Tomcat。本文中使用的是 Apache Tomcat 6.0.14。
- 获取 Apache Derby 10.6.1.0。
- Jersey 是开源的、具有产品质量的 JAX-RS 引用实现。
- Hibernate 是 Java Persistence API (JPA) 的实现。本文中使用的是 3.5.3 版本。
- 下载 IBM 产品评估版,立即下载并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。
- Roland Barcia 在博客中谈论 Web 2.0 和中间件。
- 跟随 developerWorks 成员的 web 话题分享书签。
- 快速获得答案:访问 Web 2.0 Apps 论坛。