Struts 2 + Spring 2 + JPA + AJAX

搭建 Struts2 + Spring2 + JPA + Ajax 框架

这里用 hibernate 作为 JPA(java persistent API), dojo的 Ajax 标签.

Prerequisites

首先得先下个IDE 和 Application Server 和 database

开发环境准备好以后我们来建工程

MySql

先建数据库和表……..

mysql

Code

源代码到 GoogleCode 的 这里 目录下的 /struts2_spring_hibernate/quickStart 下到, 可以先下下来看看, 或者直接按下面的自己从头做

Just Do It

用eclipse 建立工程

snag-0000

选择 动态web

snag-0001

选tomcat 6 , project 命名 为 quickstart

snag-0003

于是得到上图目录, 在 lib里加上所有需要的 jar 包

这些 jar 包然人的很….动不动缺这动不动缺那, 如果出现莫名奇妙的错误说那个 class not found 的话,

可以把找不到的 class 贴到 这里 找缺少的是哪个包.

把这些下下来的 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 配好

snag-0005

snag-0007

这里用的是tomcat6, 如果你用的服务器这里没有, 可以点 右上角的 “Download additional server adapter”

比如说 glassfish, 或者websphere communication edition

snag-0010

发布工程

snag-0011

snag-0013

底下的勾勾勾上, 可以及时发布.

启动服务器, 有 debug 和 run 模式

snag-0014

访问 http://localhost:8080/quickstart/

就可以看见页面了

snag-0015

Reference

Struts 2 + Spring 2 + JPA + AJAX

Troubleshooting guide migrating from Struts 2.0.x to 2.1.x

Advertisement

Tags: , , , , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.