搭建 Struts2 + Spring2 + JPA + Ajax 框架
这里用 hibernate 作为 JPA(java persistent API), dojo的 Ajax 标签.
Prerequisites
首先得先下个IDE 和 Application Server 和 database
开发环境准备好以后我们来建工程
MySql
先建
数据库和表……..
Code
源代码到 GoogleCode 的 这里 目录下的 /struts2_spring_hibernate/quickStart 下到, 可以先下下来看看, 或者直接按下面的自己从头做
Just Do It
用eclipse 建立工程
选择 动态web
选tomcat 6 , project 命名 为 quickstart
于是得到上图目录, 在 lib里加上所有需要的 jar 包
这些 jar 包然人的很….动不动缺这动不动缺那, 如果出现莫名奇妙的错误说那个 class not found 的话,
可以把找不到的 class 贴到 这里 找缺少的是哪个包.
- 到 Struts 2 下 2.1.6 版本
- 到 MySql JDBC Driver 下 mysql 的 驱动
- 到 Spring 2 下 spring2.5.6
- 到 Hibernate 下 Hibernate Core, Hibernate Annotations, Hibernate Entity Manager
- 下 log4j
把这些下下来的 jar 包都考到上图的 lib 目录下
起码我的 lib 里面 有这些文件
antlr-2.7.6.jar
commons-collections-3.1.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-logging-1.1.jar
dom4j-1.6.1.jar
ehcache-1.2.3.jar
ejb3-persistence.jar
freemarker-2.3.13.jar
hibernate3.jar
hibernate-annotations.jar
hibernate-cglib-repack-2.1_3.jar
hibernate-commons-annotations.jar
hibernate-entitymanager.jar
javassist-3.4.GA.jar
json-lib-2.1.jar
jta-1.1.jar
junit-3.8.1.jar
log4j-1.2.15.jar
mysql-connector-java-5.0.8-bin.jar
ognl-2.6.11.jar
slf4j-api-1.5.6.jar
slf4j-log4j12-1.5.6.jar
slf4j-simple-1.5.6.jar
spring.jar
spring-aop.jar
spring-beans.jar
spring-context.jar
spring-context-support.jar
spring-core.jar
spring-jdbc.jar
spring-jms.jar
spring-orm.jar
spring-test.jar
spring-test-2.5.6.jar
spring-tx.jar
spring-web.jar
spring-webmvc.jar
spring-webmvc-portlet.jar
spring-webmvc-struts.jar
struts2-core-2.1.6.jar
struts2-dojo-plugin-2.1.6.jar
struts2-spring-plugin-2.1.6.jar
xwork-2.1.2.jar
注意 struts2-dojo-plugin-2.1.6.jar 一定要考进来, Struts 2.1 把 dojo 标签给剔除了, 我配的时候可玩死我了. Struts 2.0 是整合的.
具体 看 这里 struts2.0 到 struts2.1 的变化
Model
model 很简单, 就单单一个 bean, 一个表
package quickstart.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Person { @Id @GeneratedValue private Integer id; private String lastName; private String firstName; public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } }
一个Person表, 属性有 id, lastName, firstName 其中 id 为 key
我们用到了 @Entity , 用这个 annotations 声明了这个class可以被持久化(persisted)
@Id 声明 id 是key
@GeneratedValue : hibernate 会生成属性 Value
DAO
一个 Dao 的接口
package quickstart.service; import java.util.List; import quickstart.model.Person; public interface PersonService { public List<Person> findAll(); public void save(Person person); public void remove(int id); public Person find(int id); }
实现
package quickstart.service; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; import org.springframework.transaction.annotation.Transactional; import quickstart.model.Person; @Transactional public class PersonServiceImpl implements PersonService { private EntityManager em; @PersistenceContext public void setEntityManager(EntityManager em) { this.em = em; } @SuppressWarnings("unchecked") public List<Person> findAll() { Query query = getEntityManager().createQuery("select p FROM Person p"); return query.getResultList(); } public void save(Person person) { if (person.getId() == null) { // new em.persist(person); } else { // update em.merge(person); } } public void remove(int id) { Person person = find(id); if (person != null) { em.remove(person); } } private EntityManager getEntityManager() { return em; } public Person find(int id) { return em.find(Person.class, id); } }
Spring 见到 @PersistenceContex 的注释会 注入 EntityManager 到这个实例.
而 @Transactional 的声明, Spring 会让这些方法都运行在一个 transaction 中.
配置 web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app id="person" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <display-name>person</display-name> <!-- Include this if you are using Hibernate --> <filter> <filter-name>Spring OpenEntityManagerInViewFilter</filter-name> <filter-class> org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter </filter-class> </filter> <filter-mapping> <filter-name>Spring OpenEntityManagerInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>struts2</filter-name> <filter-class> org.apache.struts2.dispatcher.FilterDispatcher </filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> </web-app>
所有 requests 将被 redirect 到 Struts 的 “FilterDispatcher” class, Spring’s “ContextLoaderListener” 监听
配置 Spring
在WEB-INF 地下 建立 applicationContext.xml 来配置 Sping
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <bean id="personService" class="quickstart.service.PersonServiceImpl" /> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="database" value="MYSQL" /> <property name="showSql" value="true" /> </bean> </property> </bean> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost/quickstart" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <tx:annotation-driven transaction-manager="transactionManager" /> <bean id="personAction" scope="prototype" class="quickstart.action.PersonAction"> <constructor-arg ref="personService" /> </bean> </beans>
注意 Spring 的依赖注入(Dependency Inject, 以后简称 DI), “personAction” bean 构造函数的参数设置为 “personService” bean, 为什么这样传我们后面介绍
而 scope 属性是 Spring2 新加的, 设成 “prototype” 表示每次请求实例化一个新的 Action.
“dataSource” bean 连接数据库
配置 Struts
首先新建一个 Action, Struts2 已经把 ActionBean和 Action 整合到了一起.
package quickstart.action; import java.util.List; import quickstart.model.Person; import quickstart.service.PersonService; import com.opensymphony.xwork2.Action; import com.opensymphony.xwork2.Preparable; public class PersonAction implements Preparable { private PersonService service; private List<Person> persons; private Person person; private Integer id; public PersonAction(PersonService service) { this.service = service; } public String execute() { this.persons = service.findAll(); return Action.SUCCESS; } public String save() { this.service.save(person); this.person = new Person(); return execute(); } public String remove() { service.remove(id); return execute(); } public List<Person> getPersons() { return persons; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public void prepare() throws Exception { if (id != null) person = service.find(id); } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } }
这就是一个简单的 POJO (Plain Old Java Object) 了, 也就是一个简单的bean, 没有Entity.
构造函数的参数 service 就是由 Spring 配过来的了.
然后来配置跳转, 这里 struts-config.xml 应该是扔到 src 目录下, 与 Struts1不一样.
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <constant name="struts.objectFactory" value="spring" /> <package name="person" extends="struts-default"> <action name="list" method="execute" class="personAction"> <result>pages/list.jsp</result> <result name="input">pages/list.jsp</result> </action> <action name="remove" class="personAction" method="remove"> <result>pages/list.jsp</result> <result name="input">pages/list.jsp</result> </action> <action name="save" class="personAction" method="save"> <result>pages/list.jsp</result> <result name="input">pages/list.jsp</result> </action> </package> </struts>
其中 “struts.objectFactory” 的配置很重要, 他让 Spring 来为他构造 Object, 这里面的 class=的 就是刚刚 Spring 配好的 bean 了.
Pages
下面该做页面了, 首先是一个 输出 页面
注意, Struts2.1 没有整合 dojo 的标签, 所以这里还必须另外用 dojo 的标签. 另外, Struts2.0 是整合 dojo 标签的, 具体看 这里
<%@ taglib prefix="s" uri="/struts-tags"%> <%@ taglib prefix="sx" uri="/struts-dojo-tags"%> <p>Persons</p> <s:if test="persons.size > 0"> <table> <s:iterator value="persons"> <tr id="row_<s:property value="id"/>"> <td> <s:property value="firstName" /> </td> <td> <s:property value="lastName" /> </td> <td> <s:url id="removeUrl" action="remove"> <s:param name="id" value="id" /> </s:url> <sx:a href="%{#removeUrl}"targets="persons">Remove</sx:a> <sx:a id="a_%{#id}" notifyTopics="/edit">Edit</sx:a> </td> </tr> </s:iterator> </table> </s:if>
每行数据 一个姓一个名, 一个 remove 链接, 一个 Edit 链接
<%@ taglib prefix="s" uri="/struts-tags"%> <%@ taglib prefix="sx" uri="/struts-dojo-tags" %> <html> <head> <sx:head parseContent="true"/> <script type="text/javascript"> dojo.event.topic.subscribe("/save", function(data, type, request) { if(type == "load") { dojo.byId("id").value = ""; dojo.byId("firstName").value = ""; dojo.byId("lastName").value = ""; } }); dojo.event.topic.subscribe("/edit", function(data, type, request) { if(type == "before") { var id = data.split("_")[1]; var tr = dojo.byId("row_"+id); var tds = tr.getElementsByTagName("td"); dojo.byId("id").value = id; dojo.byId("firstName").value = dojo.string.trim(dojo.dom.textContent(tds[0])); dojo.byId("lastName").value = dojo.string.trim(dojo.dom.textContent(tds[1])); } }); </script> </head> <body> <s:url action="list" id="descrsUrl"/> <div style="width: 300px;border-style: solid"> <div style="text-align: right;"> <sx:a notifyTopics="/refresh">Refresh</sx:a> </div> <sx:div id="persons" href="%{#descrsUrl}" loadingText="Loading..." listenTopics="/refresh"/> </div> <br/> <div style="width: 300px;border-style: solid"> <p>Person Data</p> <s:form action="save" validate="true"> <s:textfield id="id" name="person.id" cssStyle="display:none"/> <s:textfield id="firstName" label="Fisrt Name" name="person.firstName"/> <s:textfield id="lastName" label="Last Name" name="person.lastName"/> <sx:submit targets="persons" notifyTopics="/save"/> </s:form> </div> </body> </html>
这是 index.jsp 填入 firstName 和 lastName 点 submit 保存
Run
现在我们来跑一下程序
把application server 配好
这里用的是tomcat6, 如果你用的服务器这里没有, 可以点 右上角的 “Download additional server adapter”
比如说 glassfish, 或者websphere communication edition
发布工程
底下的勾勾勾上, 可以及时发布.
启动服务器, 有 debug 和 run 模式
访问 http://localhost:8080/quickstart/
就可以看见页面了
Reference
Struts 2 + Spring 2 + JPA + AJAX
Troubleshooting guide migrating from Struts 2.0.x to 2.1.x














