Child pages
  • Struts 2 + Spring 2 + JPA + AJAX
Skip to end of metadata
Go to start of metadata

On this tutorial we will demonstrate how to setup Struts 2 in Eclipse, and make it work with Spring, Java Persistence API (using Hibernate) and Struts 2 Ajax tags.

Hibernate is licensed under the LGPL, and any application created using Hibernate is subject to the terms of the LGPL.

Following this tutorial verbatim will require use of a Struts 2 deployment greater than 2.0.3

Prerequisites

Tomcat

Install Tomcat before going forward. See Tomcat's installation guide if you have any problem installing it.

MySql

Install and configure MySql. Create a database named "quickstart" and run the script below to create the "Person" table. Later, on applicationContext.xml, we'll use 'root' as the user name and password for the database, remember to replace those values with the right ones for your database.

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;

Get the code

Show me the code

You can just download the zipped Eclipse project, add the required dependencies to the lib folder under the /WebContent/WEB-INF/lib folder (relative to project's root folder) and import it into Eclipse.

The maven way

To run the project this way you will need maven installed.

  1. Download the zipped project
  2. Download jta jar from here.
    • Note that the Download Manager may save the file to your root drive, and it may give the file a .ZIP extension. You must rename the file to jta-1.1-classes.jar.
    • If a later version is available, update the version references in the next step.
  3. Install the jta jar file running:
    $ mvn install:install-file -DgroupId=javax.transaction -DartifactId=jta -Dversion=1.1 -Dpackaging=jar -Dfile=c:\path\to\jar\jta-1.1-classes.jar
    
  4. Bear with me, we are almost there
  5. cd into quickstart and run:
    $ mvn jetty:run
    
  6. Point your browser to http://localhost:8080/quickstart
  7. To create an eclipse project run:
    $ mvn eclipse:eclipse
    
    or (to create web project for WTP):
    mvn eclipse:eclipse -Dwtpversion=1.0
    

Doing it yourself

Create Eclipse project

  1. Open Eclipse. Seriously, you need to open Eclipse.
  2. Click File -> New -> Project. Under the "Web" folder, select "Dynamic Web Project" and click "Next".
  3. Enter the project name, "quickstart" from here on. The project will be running inside Tomcat, so we need to create a server configuration for it.
    1. Under "Target Runtime", click "New", select "Apache Tomcat 5.5" and click next.
    2. Enter Tomcat's installation directory and select an installed JRE (1.5 is required)
  4. Now you should be back to the project creation wizard, with Tomcat as your Target Runtime. Click "Next". Select "Dynamic Web Module" and "Java" facets, and click "Finish".

Dependencies

Your project should contain the folders "src", "build" and "WebContent". We are going to put all the required jars under "/WebContent/WEB-INF/lib". To add files to the "lib" folder, just copy them to ${workspace}\quickstart\WebContent\WEB-INF\lib, where ${workspace} is the location of your Eclipse workspace folder.

In the table, the version has been removed from the JAR files, since these may change in future milestone releases. Use whatever version is shipping with the indicated products.

JAR

From

License

xwork.jar

Struts 2

Apache License

struts2-core.jar

Struts 2

struts2-spring-plugin.jar

Struts 2

ognl.jar

Struts 2

freemarker.jar

Struts 2

commons-logging-api.jar

Struts 2

mysql-connector-java.jar

MySql JDBC Driver

MySQL licensing policy

spring.jar

Spring 2.0

Apache License

antlr.jar

Hibernate Core

LGPL

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

LGPL

jta.jar

Hibernate Annotations

hibernate-commons-annotations.jar

Hibernate Annotations

hibernate-annotations.jar

Hibernate Annotations

hibernate-entitymanager.jar

Hibernate Entity Manager

LGPL

javassist.jar

Hibernate Entity Manager

jboss-archive-browsing.jar

Hibernate Entity Manager

Right click on the project and select "Refresh" (to notify Eclipse of the jars that we just added).

Domain

Our domain model will consist of just a simple "Person" class with a couple of fields.

  1. Create a new class named "Person" (File -> New -> Class), and enter "quickstart.model" for the package name.
  2. Add the fields "id" (int), "firstName" (String), and lastName ("String") with their setter/getter methods.
  3. Mark your class with the "@Entity" annotation, and the "id" field with the annotations "@Id" and "@GeneratedValue".

your class will look like:

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 will let the provider know that this class can be persisted. @Id marks the "id" field as the primary key for this class. @GeneratedValue will cause the id field to be generated by the provider (Hibernate). Classes and fields are by default mapped to tables and columns with the same name, see JPA's documentation for more details.

Person service.

We will now write the class that will take care of CRUD operations on "Person" objects.

  1. Create a new interface (File -> New -> Interface), enter "PersonService" for the name, and "quickstart.service" for the namespace. Set its content to:
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);
}
  1. Create a new class (File -> New -> Class), enter "PersonServiceImpl" for the name and "quickstart.service" for the namespace. Set its content to:
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 will make Spring inject an EntityManager into the service when it is instantiated. The @PersistenceContext annotation can be placed on the field, or on the setter method. If the class is annotated as @Transactional, Spring will make sure that its methods run inside a transaction.

JPA configuration

  1. Create a folder named "META-INF" under the "src" folder.
  2. Create a file named "persistence.xml" under the "META-INF" folder and set its content to:
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 in the Spring configuration file.

Spring

  1. Update the content of web.xml under /WebContent/WEB-INF/web.xml to:
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>

This will make the container redirect all requests to Struts "FilterDispatcher" class. "index.jsp" is set as the home page, and Spring's "ContextLoaderListener" is configured as a listener.

  1. Create a file named "applicationContext.xml" under /WebContent/WEB-INF, and set its content to:
applicationContext.xml
<?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>

Note that the "class" attribute of the bean "personAction" is set to the name of the action class, and the "personService" bean will be passed as a parameter to the action constructor. Change the "url", "username" and "password" in the "dataSource" bean to the appropiate values for your database. For more details on the rest of the beans on this file, see Spring's documentation. The "scope" attribute is new in Spring 2, and it means that Spring will create a new PersonAction object every time an object of that type is requested. In Struts 2 a new action object is created to serve each request, that's why we need scope="prototype".

Struts

We will now create a simple Struts action that wraps PersonServices methods, and we will configure Struts to use Spring as the object factory.

  1. Open the new class dialog (File -> New -> Class) and enter "PersonAction" for the classname, and "quickstart.action" for the namespace. Set its content to:
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;
    }
}

Look mom my action is a simple POJO!
The "Preparable" interface instructs Struts to call the "prepare" method if the "PrepareInterceptor" is applied to the action (by default, it is). The constructor of the action takes a "PersonService" as a parameter, which Spring will take care of passing when the action is instatiated.

  1. Create a new file named "struts.xml" under the "src" folder. And set its content to:
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>

Setting "struts.objectFactory" to "spring" will force Struts to instantiate the actions using Spring, injecting all the defined dependencies on applicationContext.xml. The "class" attribute for each action alias is set to "personAction", which is the bean id that we defined on applicationContext.xml for the PersonAction class. This is all that is needed to make Struts work with Spring.

The pages

We only have two pages, "index.jsp" and "list.jsp". "list.jsp" returns a table with a list of the persons on the database.We have this list on a different page because we are going to add some AJAX to spicy it up.

  1. Create a new file named "list.jsp" under /WebContent/pages/ and set its content to:
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>

This is going to render a table with each row showing the first and last name of the person, a link to remove the person, and a link to edit. The remove link has the attribute "targets", set to "persons", which means that when the user clicks on it, an asynchronous request will be made to the "remove" action (as configured on struts.xml, "remove" points to the "remove" method in PersonAction), passing the person id as parameter.

When the edit link is clicked on, it will publish the "/edit" topic, which will trigger a javascript function to populate the fields.

  1. Create a new file named "index.jsp" under /WebContent and set its content to:
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>

Look mom no page refresh!
The div "persons" will load its content asynchronously, and will show "Loading..." while while the request is on progress (you can use the "indicator" attribute for better progress feedback), you can force it to refresh clicking on the "Refresh" link. The "submit" button, will make an asynchronous request to the action "save" ("save" method on PersonAction), and will publish the topic "/save" to which we subscribed to, using "dojo.event.topic.subscribe", to clear the input fields.

Validation

Because we don't want any John Doe on our database, we will add some basic client side validation to our form. In Struts 2, validation can be placed on xml files with the name pattern ActionName-validation.xml, located on the same package as the action. To add validation to an specific alias of an action (like a method), the validation file name follows the pattern ActionName-alias-validation.xml, where "alias" is the action alias name (in this case a method name, "save"). Add a file named "PersonAction-save-validation.xml" under /src/quickstart/action, and set its content to:

<!DOCTYPE validators PUBLIC 
    "-//Apache Struts//XWork Validator 1.0.2//EN"
    "http://struts.apache.org/dtds/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>

See the Struts documentation for details on existing validators, and how to write, and plug in, your own validators.

To run the project, Right click on your project and Run As -> Run on Server. You can debug it on the same way, Right click on the project and Debug As -> Debug on Server. Download and install Struts 2 Showcase to see more examples.

Using Toplink Essentials instead of Hibernate

  1. Add this to pom.xml
     <repositories>
         <repository>
             <id>java.net</id>
             <url>https://maven-repository.dev.java.net/nonav/repository</url>
             <layout>legacy</layout>
         </repository>
     </repositories>
    
  2. Add this to the dependencies node in pom.xml
    <dependency>
         <groupId>toplink.essentials</groupId>
         <artifactId>toplink-essentials</artifactId>
         <version>2.0-38</version>
         <exclusions>
             <exclusion>
                 <groupId>javax.transaction</groupId>
                 <artifactId>jta</artifactId>
             </exclusion>
         </exclusions>
    </dependency>
    
  3. Replace the jpaVendorAdapter element in applicationContext.xml with this:
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.TopLinkJpaVendorAdapter">
            <property name="databasePlatform" value="oracle.toplink.essentials.platform.database.MySQL4Platform" />
            <property name="generateDdl" value="true" /> 
            <property name="showSql" value="true" />
        </bean>
    </property>
    

References

Struts
Spring JPA Doc
Eclipse Dali

  • No labels

32 Comments

  1. User should be instructed to configure the proxy settings if he is trying to run this example behind firewall.

  2. To access a hibernate session in the dao, one has to use (Session)em.getDelegate().
    This especially allows for handy criteria queries.
    No sessionfactoty injection needed like in Spring.

  3. More a doubt than a post

    This entry is posted after putting quite a lot of effort to identify the cause. Searched for all possible scenarios that lead to this kind of exception. [like mailing lists, blogs, docs etc].There wasn't any resource that was of any use.                          The exception encountered is:
    Context initialization failed
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personService' defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Initialization of bean failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 0
    Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No unique bean of type [javax.persistence.EntityManagerFactory] is defined: expected single bean but found 0
        at org.springframework.beans.factory.BeanFactoryUtils.beanOfTypeIncludingAncestors(BeanFactoryUtils.java:278)

    If i am wrong to use this space available as this, please excuse me and help me any other way that would be efficient.

    Minor Clarification: To include the jar file mentioned as struts-api.jar, I searched quite some but could not find one with that name. Is it a name intended to be given to a group of jar files?

    Thanks so much for the time. Little time spent towards this would give lots of value to the weeks of time spent and consume the least from now on.

    Thanks

    Bandi 

    1. Anonymous

      I get the same error. Can anyone tell me what the problem is?

  4. The explanation of the "targets=..." attribute on the <s:a> element in the persons example is misleading. The document says "The remove link has the attribute "targets", set to "persons", which means that when the user clicks on it, an asynchronous request will be made to the "remove" action..." This is misleading because the "targets" attribute only specifies the HTML element ids will be updated with the result of the request. The action to be invoked is really only decided by the href attribute.

  5. I tried this example with oracle and jboss. It deployed successfully. When I run it and trying to add data to person. In personAction it gets Person(entity) object as null. so it gives exception null can not insert to firstname. How person bean is set when submit b'coz upon submission it will get to personAction class.

  6. PREREQUISITES
    Hibernate >= 3.2

    JAR
    xalan.jar
    serializer.jar
    hibernate-commons-annotations.jar

    LIST.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)

    Unknown macro: { if(type == "load"){ dojo.byId("id").value = ""; dojo.byId("firstName").value = ""; dojo.byId("lastName").value = ""; }

    else

    Unknown macro: { dojo.event.topic.publish("/update") }

    });
    dojo.event.topic.subscribe("/edit", function(data, type, request)

    Unknown macro: { if(type == "before"){ var id = data; 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="/actualizar">Update</s:a>
    </div>
    <s:div id="persons" theme="ajax" href="%

    Unknown macro: {descrsUrl}

    " loadingText="Loading..." listenTopics="/update" />
    </div>
    <br/>

    <div style="width: 300px;border-style: solid">
    <p>Datos</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" notifyTopics="/save"/>
    </s:form>
    </div>
    </body>
    </html>

    1. INDEX.JSP
      <%@ taglib prefix="s" uri="/struts-tags"%>
      <html>
      <head>
      <s:head theme="ajax" debug="true" />
      <script type="text/javascript">
      dojo.event.topic.subscribe("/guardar", function(data, type, request) {
      if(type == "load")

      Unknown macro: { dojo.byId("id").value = ""; dojo.byId("firstName").value = ""; dojo.byId("lastName").value = ""; dojo.event.topic.publish("/actualizar") }

      });

      dojo.event.topic.subscribe("/editar", function(data, type, request) {
      if(type == "before")

      Unknown macro: { var id = data; 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="/actualizar">Actualizar</s:a>
      </div>
      <s:div id="persons" theme="ajax" href="%

      Unknown macro: {descrsUrl}

      " loadingText="Cargando..." listenTopics="/actualizar" />
      </div>

      <br/>

      <div style="width: 300px;border-style: solid">
      <p>Datos</p>
      <s:form action="save" validate="true">
      <s:textfield id="id" name="person.id" cssStyle="display:none"/>
      <s:textfield id="firstName" label="Nombre" name="person.firstName"/>
      <s:textfield id="lastName" label="Apellidos" name="person.lastName"/>
      <s:submit theme="ajax" notifyTopics="/guardar"/>
      </s:form>
      </div>
      </body>
      </html>

      LIST.JSP
      <%@ taglib prefix="s" uri="/struts-tags"%>

      <p>Personas</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="%

      Unknown macro: {removeUrl}

      " theme="ajax" notifyTopics="/actualizar">Borrar</s:a>
      <s:a id="%

      Unknown macro: {id}

      " theme="ajax" notifyTopics="/editar">Editar</s:a>
      </td>
      </tr>
      </s:iterator>
      </table>
      </s:if>

  7. Also - your SQL script does not appear to be valid. I had to remove the '' quotes and it worked fine.

  8. I had problems with postgresql in the sequences, but I could solve it thus:

    @Entity
    public class Person {

    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "PERSON_ID_SEQ")
    @SequenceGenerator(name="PERSON_ID_SEQ", sequenceName = "person_id_seq")
    @Column(insertable=false)
    private Integer id;
    ...
    ...

    and the table:

    CREATE TABLE person
    (
    id serial NOT NULL,
    firstname character varying(45),
    lastname character varying(45),
    CONSTRAINT person_pkey PRIMARY KEY (id)
    )

    I'm learning about this...

  9. Minor correction:

    the two jar files jra.jar and javassist.jar are contained in Hibernate Core, at least in version 3.2.5.

  10. The name of the JTA downloaded file is: "jta-1_1-classes.zip".
    You suggest to rename it as ".jar" as: "jta-1.1-classes.jar".
    I think that can be useful to specify that you substituted the "_" into a "." too.

  11. The pom.xml in the current zipped file has this reference:

    <dependency>
    <groupId>javax.transaction</groupId>
    <artifactId>jta</artifactId>
    <version>1.0.1B</version>
    </dependency>

    But the tutorial reference the verison 1.1 of JTA.
    This causes an error in mvn jetty:run

  12. There is no dependency on junit in the pom, but the eclipse wtp maven thingo will show errors in the eclipse view without it.

  13. ^ i meant errors in the problems view.

  14. typo..

    will be on the Spring configuration file. -->  will be in the Spring configuration file

    1. Fixed; thanks for the feedback.

  15. This also seems a little light on explaining how the ajax/dojo stuff works. Dojo is such a fast moving toolkit you cant really expect developers to up with its latest usage.

    Also where does the dojo import in the page come from, ie where are all the dojo .js files deployed from??? I cant even see the word dojo in my file structure nor find any js files.

    1. The dojo stuff (being transformed into a plugin, btw) is being served by Struts' static filter and coming from one of the Struts jars (if not mistaken, it's been a while). If you prefer some insights, take a look at Creating a custom Dojo profile for Struts 2.0.x.

      Note that Struts 2 uses an old version of Dojo. Migration to a newer release of Dojo is on its way.

  16. There is some ambiguity regarding the WebContent directory, which doesn't exist if you use the Maven zipped project. May be an idea to clarify this.

  17. Anonymous

    hi,

    first of all, thanks for providing the tutorial.

    Thoughts to make it better -
    1. tutorial has no mention about Open session in view.
    Whether a new (hibernate) session should be opened for each request or existing session be continued.

    2. Also the dojo ajax stuff makes the tutorial confusing.

    regards,

  18. Hi,

    I had some problems running this example through maven. The final solution was to change my settings.xml file for maven so that it could download all dependencies without me having to install any files. (Found in your .m2 directory)

        <profiles>
            <profile>
                <id>Repository Proxy</id>
                <activation>
                    <activeByDefault>true</activeByDefault>
                </activation>
                <repositories>
                    <repository>
                        <id>central</id>
                        <url>http://repo1.maven.org/maven2/</url>
                    </repository>
    
                    <repository>
                        <id>javanet</id>
                        <url>http://download.java.net/maven/2/</url>
                    </repository>
    
                </repositories>
        </profile>
        </profiles>
    

    M

  19. Anonymous

    Hi,

    I got the tutorial to work; it really helped me learn to use struts with a database! However, the edit function behaves somewhat strangely. All it really does in your version is create another row in the database. I solved this for the tutorial by adding "href=%

    Unknown macro: {removeUrl}

    " to the edit link in list.jsp as well; then it removes the previous row and adds another. But it will give it a new id, so if I click on edit next to the database entry with id=2, firstName="Alan", lastName="Turing" and then change his first name to "Al", the new, edited entry will be id=3(depending on what else is in the database), firstName="Al", lastName="Turing". If I had some other data associated with "Alan Turing" (his birthday, say) via a join table, it will still be associated with the id of 2 and not the edited entry.

    If anyone has a way to solve this issue, I would love to hear it. I'm sorry for the long-winded explanation.

    Additionally, I think a great addition to the tutorial would be some JUnit test classes. It took me forever to figure out how to make mine work. Maybe if I have some time I can add that (I guess I'd have to sign up first)

    --Sally

    1. Do you have the ID field in the form? I don't really see how the merge could fail with the ID present.

      1. Anonymous

        Yes I do. I ran a JUnit test on it, and it looks like it merges just fine there. So that's pretty strange.

        1. Anonymous

          Here's the test class if you're interested:

          <java>
          package quickstart.test;

          import java.util.Iterator;
          import java.util.List;

          import org.springframework.test.jpa.AbstractJpaTests;

          import quickstart.service.PersonService;
          import quickstart.model.Person;

          public class PersonTest extends AbstractJpaTests {
          private PersonService service;

          public PersonService getService()

          Unknown macro: { return service; }

          public void setService(PersonService service)

          Unknown macro: { this.service = service; }

          protected String[] getConfigLocations() {
          return new String[]

          Unknown macro: {"applicationContext.xml"}

          ;
          //I put WEB-INF on the classpath for the project and put ejb3-persistence.jar on the classpath for the test class (under "run as-run configurations" in Eclipse)
          }

          protected void onSetUpInTransaction() throws Exception

          Unknown macro: { jdbcTemplate.execute("DELETE FROM person"); jdbcTemplate.execute("INSERT INTO person (id, firstName, lastName) VALUES (1, 'Alan', 'Turing')"); jdbcTemplate.execute("INSERT INTO person (id, firstName, lastName) VALUES (2, 'Charles', 'Babbage')"); jdbcTemplate.execute("INSERT INTO person (id, firstName, lastName) VALUES (3, 'Edsger', 'Dijkstra')"); }

          public void testSavePersonMerge()

          Unknown macro: { Person p = new Person(); p.setFirstName("Charles"); p.setLastName("Darwin"); p.setId(2); service.save(p); Person r = service.find(2); assertEquals(p.getFirstName(), r.getFirstName()); assertEquals(p.getLastName(), r.getLastName()); }

          public void testSavePersonPersist()

          Unknown macro: { Person p = new Person(); p.setFirstName("Fred"); p.setLastName("Astaire"); p.setId(4); service.save(p); int id = findPersonIdByName("Fred", "Astaire"); if(id==-1) fail("Project was not saved"); System.out.println("Person Id}

          private int findPersonIdByName(String firstName, String lastName){
          List<Person> peeps = service.findAll();
          Iterator<Person> it = peeps.iterator();
          boolean exists = false;
          Person q = new Person();
          while(it.hasNext() && !exists)

          Unknown macro: { q = it.next(); if(q.getFirstName().equals(firstName) && q.getLastName().equals(lastName)) exists = true; }

          if(exists)
          return q.getId();
          return -1;
          }
          }
          </java>

          --Sally

  20. It's better to rewrite this tutorial for 2.1.2. Attributes "debug" for tag "header", "notifyTopics" for tag "a" and "submit", "href" for "div", "targets" for "submit" and so on in JSP don't any longer exist in TLD. In this case the sample cannot be executed without errors crashing 'index.jsp'.

    1. True. I get the same errors. I'm not a newbie to Struts 1, but I am new to Struts 2 and this has jumbled me up because I have no quick idea how to replace these deprecated attributes with some alternative. If anyone has an alternative for the index.jsp page that is compatible with latest version of Struts 2, please post!

  21. Anonymous

    This is my first experience with struts 2. I got quite a shell shock that things are so fluid. It took me an entire day to get it running. I'm listing the specifics that I went through and hope it may help someone.

    My environment:
    Eclipse 3.4.2
    Struts 2.1.6
    Apache Tomcat Version 6.0.14
    Mysql 5

    My final library list:
    antlr-2.7.6.jar
    asm.jar
    asm-attrs.jar
    cglib.jar
    commons-collections-3.1.jar
    commons-fileupload-1.2.1.jar
    commons-logging-api-1.1.jar
    dom4j.jar
    ehcache-1.2.3.jar
    ejb3-persistence.jar
    freemarker-2.3.13.jar
    hibernate3.jar
    hibernate-annotations.jar
    hibernate-commons-annotations.jar
    hibernate-entitymanager.jar
    javassist.jar
    jta.jar
    mysql-connector-java-5.1.7-bin.jar
    ognl-2.6.11.jar
    slf4j-api-1.5.6.jar
    slf4j-simple-1.5.6.jar
    spring.jar
    struts2-core-2.1.6.jar
    struts2-spring-plugin-2.1.6.jar
    xwork-2.1.2.jar

    Some of the sources where these jars come from:
    slf4j-1.5.6
    commons-codec-1.3.zip
    hibernate-annotations-3.4.0.GA.zip
    hibernate-distribution-3.3.1.GA-dist.zip
    hibernate-entitymanager-3.4.0.GA.zip
    spring-framework-2.5.6.zip
    struts-2.1.6-all.zip

    Finally, I had to 1) remove "debug" from "head" in index.jsp, 2) replace theme="ajax" with theme="xhtml" in both index.jsp and list.jsp, of course, you need to make sure your db user name and password are correct in applicationContext.xml.

    The sql script didn't work for me either. I created the table on my own.

    That's all folks! Good night and good luck!

  22. Where is the pom? In the downloaded "zipped project" there was no pom.xml!

    Also, I am curious about how a maven based project can work on Tomcat with auto-republishing, etc. working all smoothly. Could anyone give me a hint? I have been searching around and haven't found an easy solution.
    thx.

    1. Hello Jane,

      Try asking your question on the mailing list, rather than the Wiki. Very few people scan the Wiki for questions (and it's not the right place to ask anyways).

      Cheers,

      Phil