Struts CRUD Example
rr-struts-crud source code
rr-struts-crud.war

NOTE: This lesson (and the above source and war) was written using an Employee object that contained a "departmentId." It makes more sense to work with a Department object nested inside of the Employee object, so I've provided updated source code and a war to reflect this change. I haven't gotten around to updating this lesson to use this latter design (any volunteers much appreicated:). The new source and war can be downloaded below:

rr-struts-crud2 source code
rr-struts-crud2.war

Introduction
Assumes some previous knowledge of Struts. This download application demonstrates a very simple application that handles your typical CRUD - Create, Retrieve, Update, Delete operations. I've noticed many people understand the basics of struts but have a difficult time putting together all the pieces. Hopefully this application will serve as a decent guide.
Requirements
This application requires an application server that implements the Servlet 2.4 and JavaServer Pages 2.0 specifications. The examples should all work on Tomcat 5.x (Discussed in next section). Please do not e-mail about getting your application to run on a server other than Tomcat. The source code (and an Ant build file) is provided for all the lessons so you should be able to build a war from the source and run it on you application server of choice.
Jars
This application uses the following jars:
  • commons-beanutils.jar
  • commons-digester.jar
  • commons-logging.jar
  • jstl-1.1.1.jar
  • standard-1.1.1.jar
  • log4j-1.2.9.jar
  • struts.jar
You can just use the jars that come with the source code for this application, but if you want the latest versions:


The latest commons jar files commons-beanutils, commons-digester, commons-logging can be found here http://jakarta.apache.org/commons/, but it's esaier to just download the latest Struts Action Framwork http://struts.apache.org/acquiring.html and just use the commons jars that are in the example apps provided with Struts. Obviously you should get the latest struts jar from there as well.

JSTL jars (standard, jstl) http://cvs.apache.org/builds/jakarta-taglibs/nightly/

log4j http://logging.apache.org/log4j/docs/download.html

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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" version="2.4">

    <display-name>Rick Reumann Struts-CRUD Demo</display-name>
    <description/>

    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>
        org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <init-param>
            <param-name>debug</param-name>
            <param-value>2</param-value>
        </init-param>
        <init-param>
            <param-name>detail</param-name>
            <param-value>2</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    
    <error-page>
        <exception-type>java.lang.Exception</exception-type>
        <location>/error.jsp</location>
    </error-page>

    <context-param>
        <param-name>
            javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>MessageResources</param-value>
    </context-param>

</web-app>
I like to define a global error.jsp in my web.xml that all my errors from the application will trickle up to. You could also define this in the struts-config but I prefer defining it in web.xml.

Notice the definition of the MessageResources file in the web.xml. Instead of using the old bean:write tag to display messages from our resources file, we're using the JSTL format tag. In order to use this tag the message bundle needs to be defined in the web.xml (*We still need to define this Resources file in the struts-config file so that errors and messages can be set up). If you look at the actual MessageResources file in the src directory you'll see it is actually called "MessageResources_en.properties." This is nice since you can provide different Locale resource files for different languages _it (Italian), _de (German), _fr (French), etc.
struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
        "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">

<struts-config>

    <form-beans>
        <form-bean name="employeeForm"
        type="net.reumann.demo.form.EmployeeForm"/>
    </form-beans>

    <action-mappings>

        <action
                path="/employeeSetUp"
                name="employeeForm"
                type="net.reumann.demo.action.EmployeeAction"
                scope="request"
                parameter="dispatch">
            <forward name="success" path="/employeeForm.jsp"/>
        </action>

        <action
                path="/employeeProcess"
                name="employeeForm"
                type="net.reumann.demo.action.EmployeeAction"
                scope="request"
                parameter="dispatch">
            <forward name="failure" path="/employeeForm.jsp"/>
            <forward name="success" path="/employees.jsp"/>
        </action>

    </action-mappings>

    <message-resources parameter="MessageResources" null="false"/>

</struts-config>
Nothing new and exciting here. I could have actually just had one mapping for this simple app /employeeProcess, but since I want the 'success' to go to a different page for setUp I just made its own mapping. A common mistake people make is that they forget that even if you use a DispatchAction you can still have multiple mappings that go to the same DispatchAction.
Interface EmployeeDao

public interface EmployeeDao {
    public List getAllEmployees();
    public Employee getEmployee(Integer id);
    public void update(Employee emp);
    public void insert(Employee emp);
    public void delete(Integer id);
}
Always a good practice to code to an Interface.
EmployeeNoDBdao

public class EmployeeNoDBdao implements EmployeeDao {
    private static List employees;
    static {
        employees = new ArrayList();
        employees.add(
            new Employee(
                new Integer(1), "John", "Doe", new Integer(36),
                new Integer(100) ) );
        employees.add(
            new Employee(
                new Integer(2), "Bob", "Smith", new Integer(25),
                new Integer(300) ) );
    }

    Log logger = LogFactory.getLog(this.getClass());

    public List getAllEmployees() {
        return employees;
    }

    public Employee getEmployee(Integer id) {
        Employee emp = null;
        Iterator iter = employees.iterator();
        while( iter.hasNext() ) {
            emp = (Employee)iter.next();
            if ( emp.getEmployeeId().equals( id ) ) {
                 break;
            }
        }
        return emp;
    }

    public void update(Employee emp) {
        Integer id = emp.getEmployeeId();
        for(int i = 0; i < employees.size(); i++ ) {
            Employee tempEmp = (Employee)employees.get(i);
            if ( tempEmp.getEmployeeId().equals( id ) ) {
                 employees.set( i, emp );
                 break;
            }
        }
    }

    public void insert(Employee emp) {
        int lastId = 0;
        Iterator iter = employees.iterator();
        while( iter.hasNext() ) {
            Employee temp = (Employee)iter.next();
            if ( temp.getEmployeeId().intValue() > lastId ) {
                lastId = temp.getEmployeeId().intValue();
            }
        }
        emp.setEmployeeId(new Integer(lastId + 1));
        employees.add(emp);
    }

    public void delete(Integer id) {
        for(int i = 0; i < employees.size(); i++ ) {
            Employee tempEmp = (Employee)employees.get(i);
            if ( tempEmp.getEmployeeId().equals( id ) ) {
                 employees.remove( i );
                 break;
            }
        }
    }
}
Since we aren't using a database or some other clean form of persistence, we're just updating a static List of employees. Obviously the above is nothing you'd really implement in real life. Possibly using a Map above would be better than a List but whatever it's not a demo on persistence:) (see the iBATIS lesson for a true persistence demo.)
EmployeeService

public interface EmployeeService {
    public List getAllEmployees();
    public void updateEmployee(Employee emp);
    public void deleteEmployee(Integer id);
    public Employee getEmployee(Integer id);
    public void insertEmployee(Employee emp);
}
Typical interface for our Service class..
EmployeeDaoService

public class EmployeeDaoService implements EmployeeService {
    private EmployeeDao dao;

    public EmployeeDaoService() {
        this.dao = new EmployeeNoDBdao();
    }

    public List getAllEmployees() {
        return dao.getAllEmployees();
    }

    public void updateEmployee(Employee emp) {
        dao.update(emp);
    }

    public void deleteEmployee(Integer id) {
        dao.delete(id);
    }

    public Employee getEmployee(Integer id) {
        return dao.getEmployee(id);
    }

    public void insertEmployee(Employee emp) {
        dao.insert(emp);
    }
}
An implementation of our EmployeeSerivce that we are using. Notice the constructor initializes our dao. If you were going to have different types of DAOs that could be used, you'd want to code something more flexible - like having a factory return you the proper dao. Or better yet, just use Spring which will inject the correct DAO for you based on what you define in a simple xml file. You might be wondering why we are even bothering with a Service class when it doesn't do anything but simply call our DAO methods. Why not just use our DAO object directly from the Action class and forget about this whole Service class? The reason I like to have this extra layer is that in a real-life more complex application there are often other business operations you might want to perform besides just calling you DAO. For example, maybe when an "update" is doine you need to call some process that sends out an e-mail or some kind of notification. If you don't use a Service class you are stuck now between coding this business logic either in your Action class or in the DAO. Neither of those places is really a good place for that kind of logic - hence we provide an extra service class to handle business rules that shouldn't be in the Action and don't belong in a DAO. Of course for rapid development, you could possibly skip the Service classes and just use the DAOs directly within your Action.
EmployeeAction

public class EmployeeAction extends DispatchAction {
    private Log logger = LogFactory.getLog(this.getClass());
    private static EmployeeService empService =
        new EmployeeDaoService();
    private static DepartmentService deptService =
        new DepartmentDaoService();

    public ActionForward getEmployees(ActionMapping mapping,
    ActionForm form, HttpServletRequest request,
    HttpServletResponse response) throws Exception {
        logger.debug("getEmployees");
        populateEmployees(request);
        return mapping.findForward(Constants.SUCCESS);
    }

    public ActionForward setUpForInsertOrUpdate(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        logger.debug("setUpForInsertOrUpdate");
        EmployeeForm employeeForm = (EmployeeForm)form;
        if (isUpdate(request, employeeForm)) {
            Integer id = Integer.valueOf(employeeForm.getEmployeeId());
            Employee employee = empService.getEmployee(id);
            BeanUtils.copyProperties(employeeForm, employee);
        }
        prep(request);
        return mapping.findForward(Constants.SUCCESS);
    }

    public ActionForward delete(ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request, HttpServletResponse response)
        throws Exception {
        logger.debug("delete");
        EmployeeForm employeeForm = (EmployeeForm)form;
        Integer id = Integer.valueOf(employeeForm.getEmployeeId());
        empService.deleteEmployee(id);
        populateEmployees(request);
        return mapping.findForward(Constants.SUCCESS);
    }

    public ActionForward insertOrUpdate(ActionMapping mapping,
    ActionForm form, HttpServletRequest request,
    HttpServletResponse response) throws Exception {
        logger.debug("insertOrUpdate");
        EmployeeForm employeeForm = (EmployeeForm)form;
        if (validationSuccessful(request, employeeForm)) {
            Employee employee = new Employee();
            BeanUtils.copyProperties(employee, employeeForm);
            if (isUpdate(request, employeeForm)) {
                logger.debug("update");
                empService.updateEmployee(employee);
            } else {
                logger.debug("insert" );
                empService.insertEmployee(employee);
            }
            populateEmployees(request);
            return mapping.findForward(Constants.SUCCESS);
        } else {
            prep(request);
            return mapping.findForward(Constants.FAILURE);
        }
    }

    private void populateEmployees(HttpServletRequest request) {
        List employees = empService.getAllEmployees();
        request.setAttribute(Constants.EMPLOYEES, employees);
        prep(request);
    }

    private void prep(HttpServletRequest request) {
        request.setAttribute(Constants.DEPARTMENTS,
        deptService.getAllDepartments());
    }

    private boolean isUpdate(HttpServletRequest request,
        EmployeeForm empForm) {
        boolean updateFlag = true;
        String id = empForm.getEmployeeId();
        if (id == null || id.trim().length() == 0 ||
            Integer.parseInt(id) == 0) {
            updateFlag = false;
        }
        request.setAttribute("updateFlag", Boolean.valueOf(updateFlag));
        return updateFlag;
    }

    private boolean validationSuccessful(HttpServletRequest request,
    EmployeeForm form) {
        boolean isOk = true;
        ActionMessages errors = new ActionMessages();
        if (form.getAge() == null ||
            form.getAge().trim().length() == 0) {
            errors.add("age", new ActionMessage("errors.required", "Age"));
        } else {
            try {
                Integer.parseInt(form.getAge());
            } catch (NumberFormatException e) {
                errors.add("age", new ActionMessage("errors.number", "Age"));
            }
        }
        if (form.getFirstName() == null ||
            form.getFirstName().trim().length() == 0) {
            errors.add("firstName",
                new ActionMessage("errors.required", "First Name"));
        }
        if (form.getLastName() == null ||
            form.getLastName().trim().length() == 0) {
            errors.add("lastName",
                new ActionMessage("errors.required", "Last Name"));
        }
        if (!errors.isEmpty()) {
            saveErrors(request, errors);
            isOk = false;
        }
        return isOk;
    }

}
I like using standard DispatchActions. Using a DispatchAction you can keep related functionality in one class. This class is our 'controller' for our Employee related tasks.
  • The service classes are intialized as statics at the top of the Action. For a more robust app you'll probably want a more flexible way to initialize the Service class implementations (ie factory, or Spring).

  • I prefer to call validation manually in my Action class. The main reason for this is it allows me to never have that annoying problem of Lists not being in scope on my form if validation fails. Notice how if (validationSuccessful(request, employeeForm)) returns false the "prep(request)" method is called. In this example prep makes sure to put my Departments list in Request scope. Of course if you were certain that the list of departments was never going to change you could use Application scope, in which case you could set that list in scope somewhere else, but I'm simply demonstrating here how to easily make sure your Lists stay in Request scope even when validation fails. (Session would also work, but I think the Session is a bad place to store form Lists - you're basically adding extra overhead for no gain.)

    If you want to use the validation framework and configure your validation rules in an xml file you could still use the approach above where you manually call validate. Thus the validationSuccessful method could be shortened to:

    
    private boolean validationSuccessful(HttpServletRequest request,
    EmployeeForm form) {
        ActionMessages errors =  form.validate();
        if (!errors.isEmpty()) {
            saveErrors(request, errors);
            return false;
        } else {
          return true;
        }
    }
    
    I tend to not use the validation framework to handle my validation because inevitably I end up with some complex validation that isn't easily handled by an xml configuration, so since I end up having to write up some validation code, I find it easier to have all of it one place versus some in a config file and some custom in a validation method. If you validation needs are definitely going to be simple, I'd use the validation framework and call the forms validate method manually as just described.
employee.jsp

<html>
<head>
    <link href="<c:url value='main.css'/>"
    rel="stylesheet" type="text/css"/>
    <title><fmt:message key="label.employees"/></title>
</head>
<body>
<div class="titleDiv"><fmt:message key="application.title"/></div>
<h1><fmt:message key="label.employees"/></h1>
<c:url var="url" scope="page" value="/employeeSetUp.do">
    <c:param name="dispatch" value="setUpForInsertOrUpdate"/>
</c:url>
<a href="${url}">Add New Employee</a>
<br/><br/>
<table class="borderAll">
<tr>
    <th><fmt:message key="label.firstName"/></th>
    <th><fmt:message key="label.lastName"/></th>
    <th><fmt:message key="label.age"/></th>
    <th><fmt:message key="label.department"/></th>
    <th> </th>
</tr>
<c:forEach var="emp" items="${employees}" varStatus="status">
    <tr class="${status.index%2==0?'even':'odd'}">
        <td class="nowrap"><c:out value="${emp.firstName}"/></td>
        <td class="nowrap"><c:out value="${emp.lastName}"/></td>
        <td class="nowrap"><c:out value="${emp.age}"/></td>
        <td class="nowrap">
            <%-- NOTE: this is NOT the best way to get the department name.
                 Just using this approach just for ease of backend objects --%>
            <c:forEach var="dept" items="${departments}">
                <c:if test="${dept.departmentId == emp.departmentId}">
                    <c:out value="${dept.name}"/>
                </c:if>
            </c:forEach>
        </td>
        <td class="nowrap">
            <c:url var="url" scope="page" value="/employeeSetUp.do">
                <c:param name="employeeId" value="${emp.employeeId}"/>
                <c:param name="dispatch" value="setUpForInsertOrUpdate"/>
            </c:url>
            <a href="${url}">Edit</a>
               
            <c:url var="url" scope="page" value="/employeeProcess.do">
                <c:param name="employeeId" value="${emp.employeeId}"/>
                <c:param name="dispatch" value="delete"/>
            </c:url>
            <a href="${url}">Delete</a>
        </td>
    </tr>
</c:forEach>
</table>
</body>
</html>
employeeForm.jsp

    
<html>
<head>
    <link href="<c:url value='main.css'/>" rel="stylesheet" type="text/css"/>
    <style>td { white-space:nowrap; }</style>
    <title><c:out value="${insertUpdateTitle}"/></title>
</head>
<body>
<div class="titleDiv"><fmt:message key="application.title"/></div>
<h1><c:out value="${insertUpdateTitle}"/></h1>
<html:form action="/employeeProcess">
<table>
 <tr>
    <td class="tdLabel"><fmt:message key="label.firstName"/>:</td>
    <td><html:text property="firstName" size="40"/>
    <html:errors property="firstName"/></td>
</tr>
<tr>
    <td class="tdLabel"><fmt:message key="label.lastName"/>:</td>
    <td><html:text property="lastName" size="40"/>
    <html:errors property="lastName"/></td>
</tr>
<tr>
    <td class="tdLabel"><fmt:message key="label.age"/>:</td>
    <td><html:text property="age" size="20"/>
    <html:errors property="age"/></td>
</tr>
 <tr>
    <td class="tdLabel"><fmt:message key="label.department"/>:</td>
    <td>
        <html:select property="departmentId">
            <c:forEach var="dept" items="${departments}">
                <html:option value="${dept.departmentId}">
                    <c:out value="${dept.name}"/>
                </html:option>
            </c:forEach>
        </html:select>
    </td>
</tr>
<tr>
    <td colspan="2">
        <html:hidden property="employeeId"/>
        <input type="hidden" name="dispatch" value="insertOrUpdate"/>
        <br/>

        <input type="submit" value="<
        fmt:message key="button.label.submit"/>" class="butStnd"/>

           
        <input type="submit" value="<
        fmt:message key="button.label.cancel"/>"  class="butStnd"
        onclick="document.employeeForm.dispatch.value='getEmployees'"/>
    </td>
</tr>
</table>
</html:form>
</body>
</html>
Code and Lesson - Rick Reumann