使用 JAX-RS、JPA 和 Dojo 创建丰富的以数据为中心的 web 应用程序

阅读更多

使用 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 players;
....
     // getters and setters........
}

这是典型的 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
				

....
........org.developerworks.soccer.model.Team
........org.developerworks.soccer.model.Player
........
............
............
............
............
............
............
............
........
....


回头看看 清单 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 getAll(){
........TypedQuery query = 
                mgr.createQuery("SELECT t FROM Team t", Team.class);
........return query.getResultList();
....}
....
....@POST
....@Consumes("application/x-www-form-urlencoded")
....@Produces("application/json")
....public Team createTeam(@FormParam("teamName") String teamName){
........Team team = new Team();
........team.setName(teamName);
........EntityTransaction txn = mgr.getTransaction();
........txn.begin();
........mgr.persist(team);
........txn.commit();
........return team;
....}
}

清单 4 显示的是类数据访问对象类,名为 TeamDao。随后我们将注释这个类,但首先我们讲解一下数据访问。该类引用 JPA 类EntityManager。这是 JPA 的中心类,并提供对底层数据库的访问。作为检索联赛中所有球队的首选方法,使用 EntityManager 来创建查询。查询使用 JPA 的查询语言,它与 SQL 相似。该查询获取所有 Teams。对于第二个方法,只要用传入的球队名创建新的Team,用 EntityManager 创建事务、保存新球队并执行事务。所有代码都是普通的 JPA 代码,因为所有类和接口都是基础 API 的一部分。

现在已经理解了 清单 4 中 JPA 部分,我们再探讨其中的 JAX-RS 部分。您首先会注意到的是使用 @Path 注释来向基于 HTTP 的客户端公开此类。/teams 字符串指定该类的相对路径。完整的 URL 路径是 /SoccerOrg/resources/teams/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 player){
........Player p = player.getValue();
........EntityTransaction txn = mgr.getTransaction();
........txn.begin();
........Team t = p.getTeam();
........Team mt = mgr.merge(t);
........p.setTeam(mt);
........mgr.persist(p);
........txn.commit();
........return p;
....}
....
....@GET
....@Produces("application/json")
....public List getAllPlayers(){
........TypedQuery query = 
............mgr.createQuery("SELECT p FROM Player p", Player.class);
........return query.getResultList();
....}
}

清单 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
				


  SoccerOrg
  
    index.html
  
  
    JAXRS-Servlet
   
 com.sun.jersey.spi.container.servlet.
ServletContainer
    
      com.sun.jersey.config.property.packages
     
 org.developerworks.soccer.model;org.developerworks.
soccer.web
    
  
  
    JAXRS-Servlet
    /resources/*
  


如您在 清单 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
				



Test Harness
        
        



....Add a Team
....
........ ........ ........ ....
....

注意在 清单 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 论坛。 

你可能感兴趣的:(dojo,xml,API)