struts2使用jpa[JPA(Java Persistence API)作为Java EE 5.0平台标准的ORM规范,将得到所有Java EE服务器的支持。]进行crud的一篇文章:
本文翻译自:http://cwiki.apache.org/S2WIKI/struts-2-spring-2-jpa-ajax.html
来 自Struts2 WiKi上的一篇文章,讲解如何在Eclipse + WTP中进行Spring2, Struts2, JPA的整合开发。很基础的说,希望对于想学习SSH的人能有一定的帮助。我在翻译的过程中,也尝试使用MyEclipse来进行整合开发。但由于水平有 限,翻译质量不敢恭维,有何错误地方,请尽量告知。谢谢!
晚上调试成功了,现在把几点需要注意的地方说一下!希望你也能顺利的通过调试,有什么问题欢迎大家一起讨论。
本指南演示了如何在Eclipse中配置Struts2,并让它与Spring2,Java Persistence API(使用Hibernate)和Struts2 Ajax 标签一起工作。
注意:按指南一步一步来需要Struts2.0.3或更高版本
Show me the code
你可以在 zipped Eclipse project下载源代码,必需的依赖库在/WebContent/WEB-INF/lib目录下,需要导入到Eclipse中。
Prerequisites
- Struts 2
- Tomcat 5.5
- Eclipse
- Eclipse WTP
- Hibernate Core
- Hibernate Annotations
- Hibernate Entity Manager
- MySql Server
- Mysql JDBC Driver
- Spring 2.0
Tomcat
首先要安装好Tomcat,如果在安装的时候遇到任何问题,请查看Tomcat的安装指南
MySql
安装并配置MySQL。创建一个名为“quickstart”的数据库,并运行下面脚本来创建“Person”表。后面在applicationContext.xml里,我们将使用"root"数据库用户名和密码,记得用你自己的数据库设置来替换它们。
CREATE
TABLE
'
quickstart
'
.
'
Person
'
(
'
id
'
INTEGER
UNSIGNED
NOT
NULL
AUTO_INCREMENT,
'
firstName
'
VARCHAR
(
45
)
NOT
NULL
,
'
lastName
'
VARCHAR
(
45
)
NOT
NULL
,
PRIMARY
KEY
(
'
id
'
)
)
ENGINE
=
InnoDB;
注:上面的DDL需要保存在文件中,然后在MySQL中导入。我直接复制然后在查询分析器中执行失败
创建Eclipse项目
- 打开Eclipse,我是认真的,你必须打开Eclipse
- 点击File -> New -> Project. 选择"Dynamic Web Project"并点击下一步(注:如果使用MyEclipse,这里不太一样)
- 输入项目名,这里我使用"quickstart"。这个项目将要在Tomcat中运行,所以我们需要为它创建应用服务器配置
- 在"Target Runtime"下面点击"New",选择"Apache Tomcat5.5"并点击下一步
- 输入Tomcat的安装路径并选择一下已安装的JRE(需要1.5)
- 现在你应该回到了项目创建向导,并且Tomcat是你的Target Runtime。点击下一步,选择"Dynamic Web Module"和"Java"facets,最后点"finish"。
(上面讲的都是Eclipse WTP中的配置,如果使用MyEclipse请自行修正)
库依赖关系
你 的项目应该包含"src","build"和"WebContent"目录。我们把所有必需的jar文件放在"/WebContent/WEB- INF/lib"目录下。请复制它们到${workspace}/quickstart/WebContent/WEB-INF/lib目录。jar文件 名的版本号已经被去除了!
Jar | From |
xwork.jar | Struts 2 |
struts2-api.jar | Struts 2 |
struts2-core.jar | Struts 2 |
struts2-Spring-plugin.jar | Struts 2 |
ognl.jar | Struts 2 |
freemarker-2.3.4.jar | Struts 2 |
mysql-connector-java.jar | MySql JDBC Driver |
spring.jar | Sping 2.0 |
antlr.jar | Hibernate Core |
asm.jar | Hibernate Core |
asm-attrs.jar | Hibernate Core |
cglib.jar | Hibernate Core |
dom4j.jar | Hibernate Core |
jdbc2_0-stdext.jar | Hibernate Core |
ehcache.jar | Hibernate Core |
hibernate3.jar | Hibernate Core |
xml-apis.jar | Hibernate Core |
commons-collections.jar | Hibernate Core |
ejb3-persistence.jar | Hibernate Annotations |
jta.jar | Hibernate Annotations |
hibernate-annotations.jar | Hibernate Annotations |
hibernate-entitymanager.jar | Hibernate Entity Manager |
javassist.jar | Hibernate Entity Manager |
jboss-archive-browsing.jar | Hibernate Entity Manager |
右击项目点“刷新”,通知Eclipse我们加入了很多的jar文件。
我 使用Struts2.0.6, Spring2.0.3, Hibernate3.2。struts2-api.jar找不到,没有也可以运行成功;Hibernate Annotations和Hibernate Entity Manager需要在Hibernate的主页上下载,不包括在Core里面;另外jta.jar和javassist.jar在Hibernate Tools里面,同样要下载;最后,上面列表并缺少一个包,因为Hibernate3.2对此有一点小小的修改,你需要把Hibernate Annotations里面的hibernate-commons-annotations.jar拷贝进来。
领域模型
我们的领域模型只有一个简单的"Person"类,它包含少量的实例变量。
- 创建一个新类并命名为"Person",然后输入"quickstart.model"作为包名。
- 添加"id"(int), "firstName"(String)和"lastName"(String)三个实例变量,并为它们加上setter/getter方法。
- 为你的类加上"@Entity"annotation,给"id" 加上 "@Id"和"@GeneratedValue" 注解
你的类如下:
Person.java
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;
}
}
@Entity让JPA服务Provider知道这个类可以被持久化。@Id标识"id"域为这个类的主键,@GeneratedValue使id域被提供者(Hibernate)自动生成。类和实例变量默认都被映射到同名的表和列上,详细情况请查看JPA文档。
Person service.
我们现在来写对"Person"对象进行CRUD操作的类。
- 创建一个接口,命名为"PersonService",包名为"quickstart.service"
PersonService.java
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);
}
2. 创建一个类,命名为"PersonServiceImpl",包名为"quickstart.service"
PersonServiceImpl.java
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);
}
}
@PersistenceContext 会让Spring在实例化的时候给服务注入一个EntityManager。@PersistenceContext注解可以放在实例变量,或者 setter方法前面。如果一个类被注解为@Transactional,Spring将会确保类的方法在运行在一个事务中。
JPA 配置
- 在"src"目录下创建一个"META-INF"目录
- 在"META-INF"目录下创建一个名为"persistence.xml"的文件。
persistence.xml
<
persistence
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"
version
="1.0"
>
<
persistence-unit
name
="punit"
>
</
persistence-unit
>
</
persistence
>
JPA configuration can be set on this file. On this example it will be empty because the datasource configuration will be on the Spring configuration file. JPA的配置信息可以在这个文件中设置。本例中该文件为空,因为数据源(datasource)配置放在Spring的配置文件中。
Spring
- 更新/WebContent/WEB-INF/web.xml文件为以下内容:
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
>
<
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
>
这会使容器将所有请求转发给Struts的"FilterDispatcher"类。"index.jsp"被设为主页,Spring的"ContextLoaderListener"被配置为listener(监听器)
1. 在/WebContent/WEB-INF目录下创建一个名为"applicationContext.xml"的文件,内容如下:
applicationContext.xml
<? xml versinotallow="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/test"
/>
<
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
>
注 意"personAction"bean的"class"属性被设为Action类的名字,并且"personService"bean会作为参数传递到 action的构造器中。改变"dataSource"Bean的"url", "username"和"passwrod"属性为你数据库的值。更多beans设置的细节,请参看Spring的文档。"scope"是Spring2 新增的属性,它意味着Spring会在该类型的对象被请求时创建一个新的PersonAction对象。在Struts2里,一个新的action对象被 创建,用来为每个请求服务,这就是我们为什么需要scope="prototype"。
Struts
现在我们需要创建一个简单的Struts action,它封装了PersonServices的方法。并且我们配置Struts使用Spring作为对象工厂。
- 打开新建类对话框,输入"PersonAction"作为类名,包名为"quickstart.action",内容如下:
PersonAction.java
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;
}
}
看,我的action是一个简单的POJO!
"Preparable" 接口指示Struts去调用"prepare"方法,如果"PrepareInterceptor"被应用在action上(默认就是这样子的)。 action的构造器有一个"PersonService"参数,在action被实例化的时候Spring会负责注入。
- 在"src"目录下创建一个名为"struts.xml"的文件,内容如下:
struts.xml
<?
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"
/>
<
constant
name
="struts.devMode"
value
="true"
/>
<
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"会强制Struts使用Spring来实例化action,并注入所有定义在 applicationContext.xml中的依赖关系。每个action别名的"class"属性被设置为"personAction",这也就是 我们在applicationContext.xml中定义的PersonAction bean。要让Struts与Spring一起工作,我们仅仅需要做上面这点事情。
页面设计
我们只有两个页面,"index.jsp"和"list.jsp"。"list.jsp"返回数据库中所有person的列表。我们把这个列表放到一个不同的页面上,是因为我们将要添加一点ajax来改进它。
- 在/WebContent/pages目录下创建一个名为"list.jsp" 的新文件,它的内容如下:
list.jsp
<% @ taglib prefix =
"
s
"
uri
=
"
/struts-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
>
< s:a
href
="%{removeUrl}"
theme
="ajax"
targets
="persons"
>
Remove
</
s:a
>
< s:a
id
="a_%{id}"
theme
="ajax"
notifyTopics
="/edit"
>
Edit
</
s:a
>
</ td
>
</ tr
>
</ s:iterator
>
</ table
>
</ s:if
>
上 面代码呈现了一个表格,每行代表一个Person,包含first name,last name,以及一个链接用来删除person,一个链接用来编辑person。删除链接有一个"targets"属性,设置为"persons",表示当 用户点击它时,一个异步的请求会被传递给"remove" action(struts.xml配置"remove"指向PersonAction的 remove方法),传递person id作为参数。
注意list.jsp是在pages目录下,如果你放在其它目录,则需要相应修改struts.xml文件
当编辑链接被点击时,它会打开/edit主题,这将触发一个javascript函数来组装各个域。
- 在/WebContent目录下创建一个新文件"index.jsp",它的内容如下:
index.jsp
<% @ taglib prefix =
"
s
"
uri
=
"
/struts-tags
"
%>
< html
>
< head
>
< s:head
theme
="ajax"
debug
="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;"
>
< s:a
theme
="ajax"
notifyTopics
="/refresh"
>
Refresh
</
s:a
>
</ div
>
< s:div
id
="persons"
theme
="ajax"
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
="First Name"
name
="person.firstName"
/>
< s:textfield
id
="lastName"
label
="Last Name"
name
="person.lastName"
/>
< s:submit
theme
="ajax"
targets
="persons"
notifyTopics
="/save"
/>
</ s:form
>
</ div
>
</ body
>
</ html
>
看,并没有页面刷新!
"personx" div会异步地装载它的内容,当请求被处理时显示"Loading..."提示(你可以使用"indicator"属性得到更佳的进度反馈),你可以通过 点击"Refresh"链接来强制页面进行刷新。"submit"按钮,向action "save"提交一个异步的请求(PersonAction的"save"方法)
Validation验证
我 们并不想我们的数据库存在任何的无名氏,所以我们给Form增加一点基本的客户端验证。在Struts2中,验证可以被放在xml文件里,命名模式为: ActionName-validation.xml,放在与action相同的包路径下。要给action的特定别名添加验证(比如方法), validation文件的命名必须为:ActionName-alias-validation.xml,这里的"alias"就是你的action的 别名(这里也就是方法名,如"save")。在src/quickstart/action目录下添加一个名为"PersonAction-save- validation.xml"文件,它的内容如下:
<! DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0.2//EN"
"http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd" >
< validators
>
< field
name
="person.firstName"
>
< field-validator
type
="requiredstring"
>
< message
>
First name is required!
</
message
>
</ field-validator
>
</ field
>
< field
name
="person.lastName"
>
< field-validator
type
="requiredstring"
>
< message
>
Last name is required!
</
message
>
</ field-validator
>
</ field
>
</ validators
>
关于现有的validator,以及如何编写和插入你自己的validator,请查看Struts文档
要 运行项目,右击你的项目并选择Run AS -> Run on Server。你也可以通过相同的办法来调试它,右击项目 Debug As -> Debug on Server。下载并安装Struts 2的 showcase演示项目来查看更多的例子。