GWT 2 Spring 3 JPA 2 Hibernate 3.5 教程

这个入门教材将教你如何实现一个简单的 web 应用程序,这个程序用 Google's Web Toolkit (GWT) 作为前台富客户端,用 spring 作为后台的服务器框架。这个例子将实现对数据库的增删改查,即CRUD (Create Retrieve Update Delete) 的功能。我们将使用以 hibernate 实现的 jpa 作为数据访问层,用 Hypersonic 作为数据库。当然,你也可以通过修改配置来选择你喜欢的数据访问层和数据库。我们将会把这个应用程序部署到 Apache – Tomcat 上。

 

  我们首选的开发环境是 Eclipse ,因此,作为先决条件,你需要有一个安装了 GWT 插件的 Eclipse 。关于GWT plugin for Eclipse 不在我们这个教程的讨论范围内。下面列出开发时需要用到的组件:

  1. Eclipse 从 这里下载
  2. GWT Plugin for Eclipse 在 这里下载
  3. Spring 框架从 这里下载
  4. Hibernate 持久层从 这里下载
  5. Hypersonic 数据库从 这里下载
  6. Apache commons-logging 包从 这里下载
  7. AOP Alliance (Java/J2EE AOP Standard) 包从 这里下载
  8. SLF4J 包从 这里下载
  9. Apache log4j 包从 这里下载
  10. 最后,下载 GWT 与 Spring 集成包 spring4gwt 从 这里下载

  在这个教程里,我们的 Eclipse 版本是 Galileo,GWT 版本是 2.0.3,Spring 版本是 3.0.1,Hibernate 版本是 3.5.2,Hypersonic 版本是 1.8.1.2,Apache commons-logging 版本是 1.1.1, AOP Alliance (Java/J2EE AOP Standard) 版本是 1.0,SLF4J 版本是 1.5.8,Apache log4j 版本是 1.2.16,spring4gwt 版本是 0.0.1。

 

  啰嗦了一大堆,现在让我们开始吧!

  1. 建立一个新的 GWT 工程,点击 File → New Web Application Project
  2. 我们将我们的工程命名为 GWTSpring。基本的包名是 com.javacodegeeks.gwtspring。因为我们仅仅使用了Google Web Toolkit ,因此在工程向导窗口中不要选择“Use Google App Engine ”。

  现在我们简单介绍一下这个工程的目录结构:

  1. /src 文件夹中包含了这个程序的全部的源文件
    • {包名}.client 子包,包含了全部仅在客户端使用的源文件
    • {包名}.server 子包,包含了全部仅在服务器端使用的源文件
    • {包名}.shared 子包,包含了全部的同时在客户端和服务器端都使用的源文件
  2. /test 文件夹包含了全部的单元测试源文件
  3. /war 文件夹包含了建立一个web应用而必须包含的基本文件

  为了正确地集成 Spring 和 GWT ,我们必须为web工程提供全部的必须的库文件。因此,复制下面列表中的文件到 web 工程的 /war/WEB-INF/lib 目录下(如果你使用不同的版本,请复制相应版本的文件)

 

  从 Spring 中复制:

  • /dist/org.springframework.expression-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.beans-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.oxm-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.jms-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.jdbc-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.core-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.context-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.asm-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.aspects-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.transaction-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.context.support-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.aop-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.orm-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.instrument-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.instrument.tomcat-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.test-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.web-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.web.portlet-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.web.servlet-3.0.1.RELEASE-A.jar
  • /dist/org.springframework.web.struts-3.0.1.RELEASE-A.jar

  从 Hibernate 中复制:

  • hibernate3.jar
  • /lib/required/antlr-2.7.6.jar
  • /lib/required/commons-collections-3.1.jar
  • /lib/required/dom4j-1.6.1.jar
  • /lib/required/javassist-3.9.0.GA.jar
  • /lib/required/jta-1.1.jar
  • /lib/required/slf4j-api-1.5.8.jar
  • /lib/jpa/hibernate-jpa-2.0-api-1.0.0.Final.jar
  • /lib/optional/c3p0/c3p0-0.9.1.jar

  从 Hypersonic 中复制:

  • /lib/hsqldb.jar

  从 Apache Commons Logging 中复制:

  • commons-logging-1.1.1.jar

  从 AOP Alliance (Java/J2EE AOP Standard) 中复制:

  • aopalliance.jar

  从 SLF4J 中复制:

  • slf4j-log4j12-1.5.8.jar

  从 Apache log4j 中复制:

  • log4j-1.2.16.jar

  从 sping4gwt 中复制:

  • spring4gwt-0.0.1.jar

  我们要注意我们 Eclipse 工程的依赖库. 下列的 jar 库将被包含到工程的构建路径中:

  • hibernate-jpa-2.0-api-1.0.0.Final.jar
  • org.springframework.beans-3.0.1.RELEASE-A.jar
  • org.springframework.context-3.0.1.RELEASE-A.jar
  • org.springframework.core-3.0.1.RELEASE-A.jar
  • org.springframework.orm-3.0.1.RELEASE-A.jar
  • org.springframework.transaction-3.0.1.RELEASE-A.jar

  下一步是对web工程进行配置,使得web工程启动时把 Spring 容器加载进来,同时也使得 spring4gwt 的客户端和服务器端能正常地通讯,客户端发出远程调用能正确地传递给服务器端的 Spring 服务。

 

  打开在 /war/WEB-INF 下的 web.xml 文件并添加下列内容:

 

  配置服务器启动时加载 Spring 容器

Xml代码   收藏代码
  1. <listener>  
  2.    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
  3. </listener>  
 

  在 servlets 那节中添加:

Xml代码   收藏代码
  1. <servlet>  
  2.     <servlet-name>springGwtRemoteServiceServlet</servlet-name>  
  3.     <servlet-class>org.spring4gwt.server.SpringGwtRemoteServiceServlet</servlet-class>  
  4. </servlet>  

 

  在 servlet-mapping 那节中添加一些 spring4gwt 的远程调用:

Xml代码   收藏代码
  1. <servlet-mapping>  
  2.     <servlet-name>springGwtRemoteServiceServlet</servlet-name>  
  3.     <url-pattern>/gwtspring/springGwtServices/*</url-pattern>  
  4. </servlet-mapping>  

 

  这里有些需要注意的地方:

  1. 在为 springGwtRemoteServiceServlet 这个 servlet 进行的配置项里,即 servlet-mapping 的子项 url-pattern 的配置变量值:/gwtspring/springGwtServices/* 里的 gwtspring,必须改为你自己的web模块名,这个模块名定义在 web 工程的 /src 目录下的 {工程名}.gwt.xml 文件(这里是 GWTSpring.gwt.xml) 里
  2. 你可以修改 spring4gwt servlet 的名字为你喜欢的 servlet 名(这里是 springGwtRemoteServiceServlet)

  接下来将会建立用于 JPA 链接数据库的配置文件 persistence.xml。这个文件必须放在 META-INF 目录里,系统运行时将会去访问到它。要满足上述所说的要求,我们需要在工程的目录 /war/WEB-INF/classes 里建立 META-INF 文件夹。实际上要做的就是新建一个名为 “resources” 的源码目录,并在里面建立一个文件夹 META-INF 即可。最后,建立 persistence.xml 文件到 /resources/META-INF 文件夹里。下面是 persistence.xml 的一个例子:

Xml代码   收藏代码
  1. <persistence xmlns="http://java.sun.com/xml/ns/persistence"  
  2.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"  
  4.     version="2.0">  
  5.   
  6.     <persistence-unit name="MyPersistenceUnit" transaction-type="RESOURCE_LOCAL">  
  7.         <provider>org.hibernate.ejb.HibernatePersistence</provider>  
  8.   
  9.         <properties>  
  10.             <property name="hibernate.hbm2ddl.auto" value="update"/>  
  11.             <property name="hibernate.show_sql" value="false"/>  
  12.             <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>  
  13.             <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>  
  14.             <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:javacodegeeks"/>  
  15.             <property name="hibernate.connection.username" value="sa"/>  
  16.             <property name="hibernate.connection.password" value=""/>  
  17.   
  18.             <property name="hibernate.c3p0.min_size" value="5"/>  
  19.             <property name="hibernate.c3p0.max_size" value="20"/>  
  20.             <property name="hibernate.c3p0.timeout" value="300"/>  
  21.             <property name="hibernate.c3p0.max_statements" value="50"/>  
  22.             <property name="hibernate.c3p0.idle_test_period" value="3000"/>  
  23.         </properties>  
  24.   
  25.     </persistence-unit>  
  26.   
  27. </persistence>  

 

  这里需要注意的是:如果你要把这个应用程序发布到一个提供 JTA 事务的 J2EE 应用服务器里,如 JBoss ,或者使用其他的数据库,如 Oracle , MySQL 等,请参考 《JBoss Spring JPA Hibernate 教程》 里的配置文件。

 

  现在让我们开始建立用于 Spring 容器的配置文件 applicationContext.xml。这个文件建立到 /war/WEB-INF 目录下。下面是一个 applicationContext.xml 的例子:

Xml代码   收藏代码
  1. <beans xmlns="http://www.springframework.org/schema/beans"  
  2.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"  
  3.     xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"  
  5.     xmlns:task="http://www.springframework.org/schema/task"  
  6.     xsi:schemaLocation="  
  7.             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
  8.             http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  9.             http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd  
  10.             http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd  
  11.             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  
  12.             http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">  
  13.   
  14.     <context:component-scan base-package="com.javacodegeeks.gwtspring"/>  
  15.   
  16.     <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>  
  17.   
  18.     <task:executor id="myExecutor" pool-size="5"/>  
  19.   
  20.     <task:scheduler id="myScheduler" pool-size="10"/>  
  21.   
  22.     <tx:annotation-driven/>  
  23.   
  24.     <bean class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean" id="entityManagerFactory">  
  25.         <property name="persistenceUnitName" value="MyPersistenceUnit"/>  
  26.     </bean>  
  27.   
  28.     <bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">  
  29.         <property name="entityManagerFactory" ref="entityManagerFactory"/>  
  30.     </bean>  
  31.   
  32. </beans>  

 

  这里有几点需要注意的:

  1. 把 context:component-scan 元素的属性 base-package 的属性值改成你自己项目的基本包名,这样 Spring的组件就可以被容器注入进来(services、DAOs 等)。
  2. 修改 entityManagerFactory bean 的属性值为你在 persistence.xml 里定义的 persistence-unit 元素的名字。
  3. 如果你要把这个应用程序发布到一个提供 JTA 事务的 J2EE 应用服务器里,如 JBoss ,或者使用其他的数据库,如 Oracle , MySQL 等,请参考 《JBoss Spring JPA Hibernate 教程》 里的配置文件。

  接下来,我们将介绍用于传送服务器与客户端之间的数据的数据传输对象(DTO),这种对象被用来访问数据库,并被 Spring 服务用它把数据传输给 GWT web 客户端来显示。

 

  DTO 是能够同时被用于服务器和客户端的对象,因此你应该在 “shared” 包里建立一个 “dto” 子包,并把 DTO 对象放在这里。下面我们将建立一个包含了雇员信息的 EmployeeDTO 对象:

 

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.shared.dto;  
  2.   
  3. import javax.persistence.Column;  
  4. import javax.persistence.Entity;  
  5. import javax.persistence.Id;  
  6. import javax.persistence.Table;  
  7.   
  8. @Entity  
  9. @Table(name = "EMPLOYEE")  
  10. public class EmployeeDTO implements java.io.Serializable {  
  11.       
  12.     private static final long serialVersionUID = 7440297955003302414L;  
  13.   
  14.     @Id  
  15.     @Column(name="employee_id")  
  16.     private long employeeId;  
  17.       
  18.     @Column(name="employee_name", nullable = false, length=30)  
  19.     private String employeeName;  
  20.       
  21.     @Column(name="employee_surname", nullable = false, length=30)  
  22.     private String employeeSurname;  
  23.       
  24.     @Column(name="job", length=50)  
  25.     private String job;  
  26.           
  27.     public EmployeeDTO() {  
  28.     }  
  29.   
  30.     public EmployeeDTO(int employeeId) {  
  31.         this.employeeId = employeeId;          
  32.     }  
  33.   
  34.     public EmployeeDTO(long employeeId, String employeeName, String employeeSurname,  
  35.             String job) {  
  36.         this.employeeId = employeeId;  
  37.         this.employeeName = employeeName;  
  38.         this.employeeSurname = employeeSurname;  
  39.         this.job = job;  
  40.     }  
  41.   
  42.     public long getEmployeeId() {  
  43.         return employeeId;  
  44.     }  
  45.   
  46.     public void setEmployeeId(long employeeId) {  
  47.         this.employeeId = employeeId;  
  48.     }  
  49.   
  50.     public String getEmployeeName() {  
  51.         return employeeName;  
  52.     }  
  53.   
  54.     public void setEmployeeName(String employeeName) {  
  55.         this.employeeName = employeeName;  
  56.     }  
  57.   
  58.     public String getEmployeeSurname() {  
  59.         return employeeSurname;  
  60.     }  
  61.   
  62.     public void setEmployeeSurname(String employeeSurname) {  
  63.         this.employeeSurname = employeeSurname;  
  64.     }  
  65.   
  66.     public String getJob() {  
  67.         return job;  
  68.     }  
  69.   
  70.     public void setJob(String job) {  
  71.         this.job = job;  
  72.     }  
  73.   
  74. }  

 

  DAO 对象是被用于执行CRUD (增删改查) 功能的对象。这是个服务器端组件,因此应该把这个对象放到工程的 “server” 子包里。现在建立一个 “dao” 子包并在里面放上我们的 DAO 对象。下面是一个 DAO 的例子:

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.server.dao;  
  2.   
  3. import javax.annotation.PostConstruct;  
  4. import javax.persistence.EntityManagerFactory;  
  5.   
  6. import org.springframework.beans.factory.annotation.Autowired;  
  7. import org.springframework.stereotype.Repository;  
  8.   
  9. import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;  
  10.   
  11. @Repository("employeeDAO")  
  12. public class EmployeeDAO extends JpaDAO<Long, EmployeeDTO> {  
  13.       
  14.     @Autowired  
  15.     EntityManagerFactory entityManagerFactory;  
  16.       
  17.     @PostConstruct  
  18.     public void init() {  
  19.         super.setEntityManagerFactory(entityManagerFactory);  
  20.     }  
  21.       
  22. }  

 

  正如你看到的那样,EmployeeDAO 继承子一个基本的 DAO 类(JpaDAO)。EmployeeDAO 对象可以包含一些关于 EmployeeDTO 的特定的查询,但全部 CRUD 操作都可以从基础类 DAO 类 (JpaDAO) 里调用。把 JpaDAO 类放到 EmployeeDAO 相同的子包 “dao” 中。以下是类 JpaDAO 的代码:

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.server.dao;   
  2.   
  3. import java.lang.reflect.ParameterizedType;   
  4. import java.util.List;   
  5.   
  6. import javax.persistence.EntityManager;   
  7. import javax.persistence.PersistenceException;   
  8. import javax.persistence.Query;   
  9.   
  10. import org.springframework.orm.jpa.JpaCallback;   
  11. import org.springframework.orm.jpa.support.JpaDaoSupport;   
  12.   
  13. public abstract class JpaDAO<K, E> extends JpaDaoSupport {   
  14.     protected Class<E> entityClass;   
  15.   
  16.     @SuppressWarnings("unchecked")   
  17.     public JpaDAO() {   
  18.         ParameterizedType genericSuperclass = (ParameterizedType) getClass()   
  19.                 .getGenericSuperclass();   
  20.         this.entityClass = (Class<E>) genericSuperclass   
  21.                 .getActualTypeArguments()[1];   
  22.     }   
  23.   
  24.     public void persist(E entity) {   
  25.         getJpaTemplate().persist(entity);   
  26.     }   
  27.   
  28.     public void remove(E entity) {   
  29.         getJpaTemplate().remove(entity);   
  30.     }   
  31.       
  32.     public E merge(E entity) {   
  33.         return getJpaTemplate().merge(entity);   
  34.     }   
  35.       
  36.     public void refresh(E entity) {   
  37.         getJpaTemplate().refresh(entity);   
  38.     }   
  39.   
  40.     public E findById(K id) {   
  41.         return getJpaTemplate().find(entityClass, id);   
  42.     }   
  43.       
  44.     public E flush(E entity) {   
  45.         getJpaTemplate().flush();   
  46.         return entity;   
  47.     }   
  48.       
  49.     @SuppressWarnings("unchecked")   
  50.     public List<E> findAll() {   
  51.         Object res = getJpaTemplate().execute(new JpaCallback() {   
  52.   
  53.             public Object doInJpa(EntityManager em) throws PersistenceException {   
  54.                 Query q = em.createQuery("SELECT h FROM " +   
  55.                         entityClass.getName() + " h");   
  56.                 return q.getResultList();   
  57.             }   
  58.               
  59.         });   
  60.           
  61.         return (List<E>) res;   
  62.     }   
  63.   
  64.     @SuppressWarnings("unchecked")   
  65.     public Integer removeAll() {   
  66.         return (Integer) getJpaTemplate().execute(new JpaCallback() {   
  67.   
  68.             public Object doInJpa(EntityManager em) throws PersistenceException {   
  69.                 Query q = em.createQuery("DELETE FROM " +   
  70.                         entityClass.getName() + " h");   
  71.                 return q.executeUpdate();   
  72.             }   
  73.               
  74.         });   
  75.     }  
  76.       
  77. }  

 

  最后,我们将建立为 GWT 客户端访问的服务接口与实现类。这个服务接口将会被服务器端和客户端同时访问,因此我们把它放到我们工程的 “shared” 子包下。现在建立一个 “services” 子包,并准备在里面放置服务接口类。下面是该接口的例子代码:

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.shared.services;  
  2.   
  3. import com.google.gwt.user.client.rpc.RemoteService;  
  4. import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;  
  5.   
  6. import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;  
  7.   
  8. @RemoteServiceRelativePath("springGwtServices/employeeService")  
  9. public interface EmployeeService extends RemoteService {  
  10.       
  11.     public EmployeeDTO findEmployee(long employeeId);  
  12.     public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;  
  13.     public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;  
  14.     public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception;  
  15.     public void deleteEmployee(long employeeId) throws Exception;  
  16.       
  17. }  

 

  下面几点注意事项:

  1. GWT 客户端只能通过异步的远程过程(RPCs)调用来调用服务器端功能服务的。这个服务接口必须继承 RemoteService 接口. 这个接口的异步调用部分也必须能够提供异步通讯的功能(后面介绍)。
  2. 我们通过给这个接口加入注解,以使得这个服务能够被访问。因为这个服务是一个 Spring 服务,所以我们希望 spring4gwt 拦截这个 RPC 调用来执行一个 Spring 服务调用。为了实现这个需求,我们在前面提及的 web.xml 里定义了一个可以通过用 “springGwtRemoteServiceServlet” 来调用相对路径。
  3. 这个服务名定义在 “RemoteServiceRelativePath” 的注解里,这里的 “employeeService”,必须匹配一个Spring 服务 bean 名。我们将在下面的服务实现类里定义这个 Spring 服务 bean名。

  下面是这个异步服务接口代码:

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.shared.services;  
  2.   
  3. import com.google.gwt.user.client.rpc.AsyncCallback;  
  4. import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;  
  5.   
  6. public interface EmployeeServiceAsync {  
  7.   
  8.     void deleteEmployee(long employeeId, AsyncCallback<Void> callback);  
  9.   
  10.     void findEmployee(long employeeId, AsyncCallback<EmployeeDTO> callback);  
  11.   
  12.     void saveEmployee(long employeeId, String name, String surname,  
  13.             String jobDescription, AsyncCallback<Void> callback);  
  14.   
  15.     void saveOrUpdateEmployee(long employeeId, String name, String surname,  
  16.             String jobDescription, AsyncCallback<Void> callback);  
  17.   
  18.     void updateEmployee(long employeeId, String name, String surname,  
  19.             String jobDescription, AsyncCallback<Void> callback);  
  20.   
  21. }  

 

  这个服务实现类是一个服务器端组件,因此我们把它放到我们工程的 “server” 子包里。现在建立这个 “services” 子包,并把这个服务实现类放到里面。下面是这个服务实现类的代码:

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.server.services;   
  2.   
  3. import javax.annotation.PostConstruct;   
  4. import javax.annotation.PreDestroy;   
  5.   
  6. import org.springframework.beans.factory.annotation.Autowired;   
  7. import org.springframework.stereotype.Service;   
  8. import org.springframework.transaction.annotation.Propagation;   
  9. import org.springframework.transaction.annotation.Transactional;   
  10.   
  11. import com.javacodegeeks.gwtspring.server.dao.EmployeeDAO;   
  12. import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;   
  13. import com.javacodegeeks.gwtspring.shared.services.EmployeeService;   
  14.   
  15. @Service("employeeService")   
  16. public class EmployeeServiceImpl implements EmployeeService {   
  17.       
  18.     @Autowired   
  19.     private EmployeeDAO employeeDAO;   
  20.   
  21.     @PostConstruct   
  22.     public void init() throws Exception {   
  23.     }   
  24.       
  25.     @PreDestroy   
  26.     public void destroy() {   
  27.     }   
  28.   
  29.     public EmployeeDTO findEmployee(long employeeId) {   
  30.           
  31.         return employeeDAO.findById(employeeId);   
  32.           
  33.     }   
  34.       
  35.     @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)   
  36.     public void saveEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {   
  37.               
  38.         EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);   
  39.           
  40.         if(employeeDTO == null) {   
  41.             employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);   
  42.             employeeDAO.persist(employeeDTO);   
  43.         }   
  44.           
  45.     }   
  46.       
  47.     @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)   
  48.     public void updateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {   
  49.           
  50.         EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);   
  51.           
  52.         if(employeeDTO != null) {   
  53.             employeeDTO.setEmployeeName(name);   
  54.             employeeDTO.setEmployeeSurname(surname);   
  55.             employeeDTO.setJob(jobDescription);   
  56.         }   
  57.   
  58.     }   
  59.       
  60.     @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)   
  61.     public void deleteEmployee(long employeeId) throws Exception {   
  62.           
  63.         EmployeeDTO employeeDTO = employeeDAO.findById(employeeId);   
  64.           
  65.         if(employeeDTO != null)   
  66.             employeeDAO.remove(employeeDTO);   
  67.   
  68.     }   
  69.       
  70.     @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)   
  71.     public void saveOrUpdateEmployee(long employeeId, String name, String surname, String jobDescription) throws Exception {   
  72.           
  73.         EmployeeDTO employeeDTO = new EmployeeDTO(employeeId, name,surname, jobDescription);   
  74.           
  75.         employeeDAO.merge(employeeDTO);   
  76.           
  77.     }   
  78.   
  79. }   

 

  这里需要注意要点是:

  1. 我们这个类上添加了注解 @Service("employeeService"),就可以让这个成为一个名为 "exampleService" 的Spring 服务,Spring 容器将会在启动的时候实例化全部的服务。
  2. 我们使用注解 @Autowire 来把一个 DAO 实例注入到 "employeeService" 里。Spring 容器会把适当的 DAO 实例注入到这个 employeeDAO 属性中。
  3. 我们定义 @PostConstruct 这个注解在方法上,用来指示当Spring 容器初始化完后(全部的依赖注入完成)执行这个方法,定义 @PreDestroy 注解在方法上,指示 Spring 容器在销毁这个服务前执行这个方法。
  4. 我们把 @Transactional 定义在所有需要进行数据库更新的方法上(插入、修改和删除)
  5. 我们不应在对数据库对象查询和没有对数据库进行操作的方法中使用 @Transactional 注解(除非被查询的对象里有延迟加载的数据 - 参考后面的解释)。这是因为每当你调用一个被标注为启用事务的方法时,Spring 容器都会使用 JPA 的实体管理器进行事务管理,这会消耗一部分性能,从而导致性能降低。
  6. 对于那些查询延迟加载数据的方法,你应该加上 @Transactional 注解,指示 Spring 维持 Hibernate 的  session 在整个方法调用中一直保持打开的状态。
  7. 事务行为仅在客户端调用服务的时候提供,而在内部调用时不提供事务行为。例如,如果一个客户调用了一个没有用事务标注的操作,且在这个服务中,这个操作又调用了一个用事务标注的操作,那么整个联合的操作都是不提供事务的。

  终于完成了!但我们还将要开发 GWT 用户接口来访问我们的 Spring 服务。 尽管事实上 GWT 的用户接口开发不属于我们这个教程的讨论范围,但我们还是提供了一个基本的用户接口来展示这些如何调用这些 Spring 服务。

  先定位到你的 GWT 应用程序的入口点。这个文件应该这样命名:{工程名}.java,在我们这个程序里是 GWTSpring.java,并把它放到 “client” 子包下或我们的主包下,以下是这个类的代码:

Java代码   收藏代码
  1. package com.javacodegeeks.gwtspring.client;  
  2.   
  3. import com.google.gwt.core.client.EntryPoint;  
  4. import com.google.gwt.core.client.GWT;  
  5. import com.google.gwt.event.dom.client.ClickEvent;  
  6. import com.google.gwt.event.dom.client.ClickHandler;  
  7. import com.google.gwt.event.dom.client.KeyCodes;  
  8. import com.google.gwt.event.dom.client.KeyUpEvent;  
  9. import com.google.gwt.event.dom.client.KeyUpHandler;  
  10. import com.google.gwt.user.client.rpc.AsyncCallback;  
  11. import com.google.gwt.user.client.ui.Button;  
  12. import com.google.gwt.user.client.ui.DialogBox;  
  13. import com.google.gwt.user.client.ui.HTML;  
  14. import com.google.gwt.user.client.ui.Label;  
  15. import com.google.gwt.user.client.ui.RootPanel;  
  16. import com.google.gwt.user.client.ui.TextBox;  
  17. import com.google.gwt.user.client.ui.VerticalPanel;  
  18. import com.javacodegeeks.gwtspring.shared.dto.EmployeeDTO;  
  19. import com.javacodegeeks.gwtspring.shared.services.EmployeeService;  
  20. import com.javacodegeeks.gwtspring.shared.services.EmployeeServiceAsync;  
  21.   
  22. /** 
  23.  * Entry point classes define <code>onModuleLoad()</code>. 
  24.  */  
  25. public class GWTSpring implements EntryPoint {  
  26.     /** 
  27.      * The message displayed to the user when the server cannot be reached or 
  28.      * returns an error. 
  29.      */  
  30.     private static final String SERVER_ERROR = "An error occurred while "  
  31.             + "attempting to contact the server. Please check your network "  
  32.             + "connection and try again. The error is : ";  
  33.       
  34.     /** 
  35.      * Create a remote service proxy to talk to the server-side Employee service. 
  36.      */  
  37.     private final EmployeeServiceAsync employeeService = GWT  
  38.             .create(EmployeeService.class);  
  39.   
  40.     /** 
  41.      * This is the entry point method. 
  42.      */  
  43.     public void onModuleLoad() {  
  44.         final Button saveOrUpdateButton = new Button("SaveOrUpdate");  
  45.         final Button retrieveButton = new Button("Retrieve");  
  46.         final TextBox employeeInfoField = new TextBox();  
  47.         employeeInfoField.setText("Employee Info");  
  48.         final TextBox employeeIdField = new TextBox();  
  49.         final Label errorLabel = new Label();  
  50.   
  51.         // We can add style names to widgets  
  52.         saveOrUpdateButton.addStyleName("sendButton");  
  53.         retrieveButton.addStyleName("sendButton");  
  54.   
  55.         // Add the nameField and sendButton to the RootPanel  
  56.         // Use RootPanel.get() to get the entire body element  
  57.         RootPanel.get("employeeInfoFieldContainer").add(employeeInfoField);  
  58.         RootPanel.get("updateEmployeeButtonContainer").add(saveOrUpdateButton);  
  59.         RootPanel.get("employeeIdFieldContainer").add(employeeIdField);  
  60.         RootPanel.get("retrieveEmployeeButtonContainer").add(retrieveButton);  
  61.         RootPanel.get("errorLabelContainer").add(errorLabel);  
  62.   
  63.         // Focus the cursor on the name field when the app loads  
  64.         employeeInfoField.setFocus(true);  
  65.         employeeInfoField.selectAll();  
  66.   
  67.         // Create the popup dialog box  
  68.         final DialogBox dialogBox = new DialogBox();  
  69.         dialogBox.setText("Remote Procedure Call");  
  70.         dialogBox.setAnimationEnabled(true);  
  71.         final Button closeButton = new Button("Close");  
  72.         // We can set the id of a widget by accessing its Element  
  73.         closeButton.getElement().setId("closeButton");  
  74.         final Label textToServerLabel = new Label();  
  75.         final HTML serverResponseLabel = new HTML();  
  76.         VerticalPanel dialogVPanel = new VerticalPanel();  
  77.         dialogVPanel.addStyleName("dialogVPanel");  
  78.         dialogVPanel.add(new HTML("<b>Sending request to the server:</b>"));  
  79.         dialogVPanel.add(textToServerLabel);  
  80.         dialogVPanel.add(new HTML("  
  81. <b>Server replies:</b>"));  
  82.         dialogVPanel.add(serverResponseLabel);  
  83.         dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);  
  84.         dialogVPanel.add(closeButton);  
  85.         dialogBox.setWidget(dialogVPanel);  
  86.   
  87.         // Add a handler to close the DialogBox  
  88.         closeButton.addClickHandler(new ClickHandler() {  
  89.             public void onClick(ClickEvent event) {  
  90.                 dialogBox.hide();  
  91.                 saveOrUpdateButton.setEnabled(true);  
  92.                 saveOrUpdateButton.setFocus(true);  
  93.                 retrieveButton.setEnabled(true);  
  94.             }  
  95.         });  
  96.   
  97.         // Create a handler for the saveOrUpdateButton and employeeInfoField  
  98.         class SaveOrUpdateEmployeeHandler implements ClickHandler, KeyUpHandler {  
  99.             /** 
  100.              * Fired when the user clicks on the saveOrUpdateButton. 
  101.              */  
  102.             public void onClick(ClickEvent event) {  
  103.                 sendEmployeeInfoToServer();  
  104.             }  
  105.   
  106.             /** 
  107.              * Fired when the user types in the employeeInfoField. 
  108.              */  
  109.             public void onKeyUp(KeyUpEvent event) {  
  110.                 if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {  
  111.                     sendEmployeeInfoToServer();  
  112.                 }  
  113.             }  
  114.   
  115.             /** 
  116.              * Send the employee info from the employeeInfoField to the server and wait for a response. 
  117.              */  
  118.             private void sendEmployeeInfoToServer() {  
  119.                 // First, we validate the input.  
  120.                 errorLabel.setText("");  
  121.                 String textToServer = employeeInfoField.getText();  
  122.   
  123.                 // Then, we send the input to the server.  
  124.                 saveOrUpdateButton.setEnabled(false);  
  125.                 textToServerLabel.setText(textToServer);  
  126.                 serverResponseLabel.setText("");  
  127.   
  128.                 String[] employeeInfo = textToServer.split(" ");  
  129.                   
  130.                 long employeeId = Long.parseLong(employeeInfo[0]);  
  131.                 String employeeName = employeeInfo[1];  
  132.                 String employeeSurname = employeeInfo[2];  
  133.                 String employeeJobTitle = employeeInfo[3];  
  134.                   
  135.                 employeeService.saveOrUpdateEmployee(employeeId, employeeName, employeeSurname, employeeJobTitle,   
  136.                         new AsyncCallback<Void>() {  
  137.                             public void onFailure(Throwable caught) {  
  138.                                 // Show the RPC error message to the user  
  139.                                 dialogBox  
  140.                                         .setText("Remote Procedure Call - Failure");  
  141.                                 serverResponseLabel  
  142.                                         .addStyleName("serverResponseLabelError");  
  143.                                 serverResponseLabel.setHTML(SERVER_ERROR + caught.toString());  
  144.                                 dialogBox.center();  
  145.                                 closeButton.setFocus(true);  
  146.                             }  
  147.   
  148.                             public void onSuccess(Void noAnswer) {  
  149.                                 dialogBox.setText("Remote Procedure Call");  
  150.                                 serverResponseLabel  
  151.                                         .removeStyleName("serverResponseLabelError");  
  152.                                 serverResponseLabel.setHTML("OK");  
  153.                                 dialogBox.center();  
  154.                                 closeButton.setFocus(true);  
  155.                             }  
  156.                         });  
  157.             }  
  158.         }  
  159.           
  160.         // Create a handler for the retrieveButton and employeeIdField  
  161.         class RetrieveEmployeeHandler implements ClickHandler, KeyUpHandler {  
  162.             /** 
  163.              * Fired when the user clicks on the retrieveButton. 
  164.              */  
  165.             public void onClick(ClickEvent event) {  
  166.                 sendEmployeeIdToServer();  
  167.             }  
  168.   
  169.             /** 
  170.              * Fired when the user types in the employeeIdField. 
  171.              */  
  172.             public void onKeyUp(KeyUpEvent event) {  
  173.                 if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {  
  174.                     sendEmployeeIdToServer();  
  175.                 }  
  176.             }  
  177.   
  178.             /** 
  179.              * Send the id from the employeeIdField to the server and wait for a response. 
  180.              */  
  181.             private void sendEmployeeIdToServer() {  
  182.                 // First, we validate the input.  
  183.                 errorLabel.setText("");  
  184.                 String textToServer = employeeIdField.getText();  
  185.   
  186.                 // Then, we send the input to the server.  
  187.                 retrieveButton.setEnabled(false);  
  188.                 textToServerLabel.setText(textToServer);  
  189.                 serverResponseLabel.setText("");  
  190.   
  191.                 employeeService.findEmployee(Long.parseLong(textToServer),    
  192.                         new AsyncCallback<EmployeeDTO>() {  
  193.                             public void onFailure(Throwable caught) {  
  194.                                 // Show the RPC error message to the user  
  195.                                 dialogBox  
  196.                                         .setText("Remote Procedure Call - Failure");  
  197.                                 serverResponseLabel  
  198.                                         .addStyleName("serverResponseLabelError");  
  199.                                 serverResponseLabel.setHTML(SERVER_ERROR + caught.toString());  
  200.                                 dialogBox.center();  
  201.                                 closeButton.setFocus(true);  
  202.                             }  
  203.   
  204.                             public void onSuccess(EmployeeDTO employeeDTO) {  
  205.                                 dialogBox.setText("Remote Procedure Call");  
  206.                                 serverResponseLabel  
  207.                                         .removeStyleName("serverResponseLabelError");  
  208.                                 if(employeeDTO != null)  
  209.                                     serverResponseLabel.setHTML("Employee Information Id : " + employeeDTO.getEmployeeId() + " Name : " + employeeDTO.getEmployeeName() + " Surname : " + employeeDTO.getEmployeeSurname() + " Job Title : " + employeeDTO.getJob());  
  210.                                 else  
  211.                                     serverResponseLabel.setHTML("No employee with the specified id found");  
  212.                                 dialogBox.center();  
  213.                                 closeButton.setFocus(true);  
  214.                             }  
  215.                         });  
  216.             }  
  217.         }  
  218.   
  219.         // Add a handler to send the employee info to the server  
  220.         SaveOrUpdateEmployeeHandler saveOrUpdateEmployeehandler = new SaveOrUpdateEmployeeHandler();  
  221.         saveOrUpdateButton.addClickHandler(saveOrUpdateEmployeehandler);  
  222.         employeeInfoField.addKeyUpHandler(saveOrUpdateEmployeehandler);  
  223.           
  224.         // Add a handler to send the employee id to the server  
  225.         RetrieveEmployeeHandler retrieveEmployeehandler = new RetrieveEmployeeHandler();  
  226.         retrieveButton.addClickHandler(retrieveEmployeehandler);  
  227.         employeeIdField.addKeyUpHandler(retrieveEmployeehandler);  
  228.     }  
  229. }  

 

  正如你所见到的那样,对于客户端来说,调用 Spring 服务就像调用传统的 GWT 服务那样调用。

  最后,打开你工程的 web 主文件。这个文件应该象{工程名}.html 这样来命名,在我们这里的命名是 GWTSpring.html,并把这个文件放到 /war 目录下。下面是这个文件的代码

Html代码   收藏代码
  1. <!doctype html>  
  2. <!-- The DOCTYPE declaration above will set the    -->  
  3. <!-- browser's rendering engine into               -->  
  4. <!-- "Standards Mode". Replacing this declaration  -->  
  5. <!-- with a "Quirks Mode" doctype may lead to some -->  
  6. <!-- differences in layout.                        -->  
  7.   
  8. <html>  
  9.   <head>  
  10.     <meta http-equiv="content-type" content="text/html; charset=UTF-8">  
  11.   
  12.     <!--                                                               -->  
  13.     <!-- Consider inlining CSS to reduce the number of requested files -->  
  14.     <!--                                                               -->  
  15.     <link type="text/css" rel="stylesheet" href="GWTSpring.css">  
  16.   
  17.     <!--                                           -->  
  18.     <!-- Any title is fine                         -->  
  19.     <!--                                           -->  
  20.     <title>Spring GWT Web Application Starter Project</title>  
  21.       
  22.     <!--                                           -->  
  23.     <!-- This script loads your compiled module.   -->  
  24.     <!-- If you add any GWT meta tags, they must   -->  
  25.     <!-- be added before this line.                -->  
  26.     <!--                                           -->  
  27.     <script type="text/javascript" language="javascript" src="gwtspring/gwtspring.nocache.js"></script>  
  28.   </head>  
  29.   
  30.   <!--                                           -->  
  31.   <!-- The body can have arbitrary html, or      -->  
  32.   <!-- you can leave the body empty if you want  -->  
  33.   <!-- to create a completely dynamic UI.        -->  
  34.   <!--                                           -->  
  35.   <body>  
  36.   
  37.     <!-- OPTIONAL: include this if you want history support -->  
  38.     <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>  
  39.       
  40.     <!-- RECOMMENDED if your web app will not function without JavaScript enabled -->  
  41.     <noscript>  
  42.       <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">  
  43.         Your web browser must have JavaScript enabled  
  44.         in order for this application to display correctly.  
  45.       </div>  
  46.     </noscript>  
  47.   
  48.     <h1>Spring GWT Web Application Starter Project</h1>  
  49.   
  50.     <table align="center">  
  51.       <tr>  
  52.         <td colspan="2" style="font-weight:bold;">Please enter employee info (id name surname job):</td>          
  53.       </tr>  
  54.       <tr>  
  55.         <td id="employeeInfoFieldContainer"></td>  
  56.         <td id="updateEmployeeButtonContainer"></td>  
  57.       </tr>  
  58.       <tr>  
  59.       <tr>  
  60.         <td colspan="2" style="font-weight:bold;">Please enter employee id:</td>          
  61.       </tr>  
  62.       <tr>  
  63.         <td id="employeeIdFieldContainer"></td>  
  64.         <td id="retrieveEmployeeButtonContainer"></td>  
  65.       </tr>  
  66.       <tr>  
  67.         <td colspan="2" style="color:red;" id="errorLabelContainer"></td>  
  68.       </tr>  
  69.     </table>  
  70.   </body>  
  71. </html>  

 

  要编译这个应用,右键点击工程名并选择 Run As → Compile GWT Application。

  要发布这个应用,只需复制这个 /war 目录到 Apache – Tomcat 的 “webapps” 目录下。你可以修改 war 这个目录名为你喜欢的任何名字,最好是把它命名为这个工程的名字,例如 GWTSpring。

  在浏览器地址栏输入以下地址即可浏览这个应用:

http://localhost:8080/GWTSpring/

  如果一切正常的话,你就可以看到这个应用的 web主页了,里面有两个文本框,每个文本框后面有一个按钮。在第一给文本框中,你可以保存或更新职员数据倒数据库中。录入一个 id、名字、姓氏和一个工作的描述,用一个空白符来分开。点击 “SaveOrUpdate” 按钮,提供的数据库就将被保存到数据库中。对应已经存在的职员数据(相同的 id),将会执行更新。第二个文本框用于查询存在的职员数据。输入一个职员的 id 并点击 “Retrieve” 按钮,如果这个职员存在的话,你就可以看到这个职员的 id、名字、姓氏和工作描述。

  哦,这真是一个大的教程!

  你可以从 here 下载这个应用,前面所说的第三方库并没用包含在里面。


  希望你喜欢这个教程。


  Justin

 

译者注:这是我第一次翻译外文技术文章。让我感触较深的是,自己看懂这篇文章容易,但要翻译起来却要花费很多的时间。往往原文的一句话要翻译出来,必须对这句话进行多遍的阅读,并在阅读的过程中慢慢组织成我们的语言。但最后翻译完成后,还是有很多地方感觉很生涩。不得已,还把少量的不影响阅读和理解的段落进行了简化,如关于事务的介绍部分。如果你需要了解更多,或觉得这篇文章难以理解,请阅读原文,或给我留言,也欢迎您把我翻译错误的地方指出来。

 

而促使我翻译这篇文章的动机是,前些日子里,我在思考,为什么我的生活总过得很窘迫?(通俗点说就是为什么我总是没钱?)以一个程序员的思维,我反过来问自己,我凭什么有钱?我相信这个世界有一个真理,就是要有收获,就必须有付出。那么,我付出了什么?我付出了很多的时间去学习?但我为这个世界做了什么?为家人、为朋友、为认识和不认识的人做了什么?我答不上来。相反地,在思考过程中,我觉得我从这个世界里,获得了很多我想要的东西,比如知识、亲情、友情、爱情、食物、工作等等。那么,不管如何,我是不是该做点什么,不是为了自己,而是为别人而做的事情?我想,是的,我要为别人做点事,做点简单的事,做点力所能及的事。不为钱财名誉,不为个人利益,只为反哺这个世界。于是,就是有了这篇文章。感谢你阅读这篇文章,希望能给你一点帮助。当然,如果你愿意,希望你也能为这个世界的做点自己力所能及的事。谢谢。

你可能感兴趣的:(spring,Hibernate,Web,gwt)