Java EE Servlet tutorial : Adding create, update and delete to the bookstore listing

From Resin 4.0 Wiki

Jump to: navigation, search

This tutorial is part of Java EE Tutorial covering JSP_2.2, and Servlets 3.0.

Contents

Java EE Servlet Tutorial: Implementing a basic CRUD listing

We left off with a basic listing. BookListServlet used a BookRepository object (DAO) to load a list of books and then delegated to book-list.jsp to render the book listing.

In this step, we want to add a link to the book listing so when the user clicks it a form gets rendered so that they can edit the details of book. Also we want an add link so that the end user can add a new book to the listing.

Adding a link to the book listing to edit a book

Change the code that looks like this in book-list.jsp:

...
	<c:forEach var="book" items="${books}">
		<tr>
			<td>${book.title}</td>
...

To this


Adding an edit book link to book-list.jsp listing

...
	<c:forEach var="book" items="${books}">
	     <tr>
			<td><a href="${pageContext.request.contextPath}/book?id=${book.id}">${book.title}</a></td>
...			

The EL expression ${pageContext.request.contextPath} refers to the URI you have the war file mapped to in the Servlet container. The default URI for a webapp is its war file name so our war file name is bookstore so a rendered URL will look like this:

	...
		<tr>
			<td><a href="/bookstore/book?id=0">War and Peace</a></td>
	...
		<tr>
			<td><a href="/bookstore/book?id=1">Pride and Prejudice</a></td>
	...

The link /bookstore/book/ (with slash) loads the book listing. The link /bookstore/book?id=1 loads the form page to edit an existing book. To add a new book, we will use the link /bookstore/book.

Adding a link to the book listing to add a book

Adding an add book link to book-list.jsp listing

Before the table add this HTML code to add a new book:

...
<a href="${pageContext.request.contextPath}/book">Add Book</a>
...			

Now we have to links going to the URI /book, we need a Servlet that handles the links. The BookEditorServlet handles both add and the edit book functions.

Servlet doGet to load a Book form

BookEditorServlet.java doGet

package com.bookstore.web;

...

@WebServlet("/book")
public class BookEditorServlet extends HttpServlet {

	@Inject
	private BookRepository bookRepo;
		
	private SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
	
    ...
	/** Prepare the book form before we display it. */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		String id = request.getParameter("id");

		if (id != null && !id.isEmpty()) {
			Book book = bookRepo.lookupBookById(id);
			request.setAttribute("book", book);
			request.setAttribute("bookPubDate", dateFormat.format(book.getPubDate()));
		}

		/* Redirect to book-form. */
		getServletContext().getRequestDispatcher("/WEB-INF/pages/book-form.jsp").forward(
				request, response);

	}
}

Notice we use @WebServlet("/book") to map BookEditorServlet to the URI /book. It is common to load a form from a doGet method, and to handle the form submission via doPost. Following REST and HTTP principles GET operations reads data, and POST data modifies data. The doGet method uses the id being empty or not to determine if this is a load "Add Employee Form" or load "Update Employee Form" operation. The doGet method also converts the date into a format that is easy for the end user to modify and it also maps the book into request scope as follows:

BookEditorServlet.java doGet() delegate to book-form.jsp page

			Book book = bookRepo.lookupBookById(id);
			request.setAttribute("book", book);
			request.setAttribute("bookPubDate", dateFormat.format(book.getPubDate()));



In the model 2 architecture, the Servlet tier prepares the form data before a form loads to be rendered and edited correctly.

To render the HTML form, the servlet delegates to book-form.jsp as follows:

BookEditorServlet.java doGet() delegate to book-form.jsp page

		/* Redirect to book-form. */
		getServletContext().getRequestDispatcher("/WEB-INF/pages/book-form.jsp").forward(
				request, response);

Rendering the book form HTML

The book-form.jsp has the HTML code to render a form as follows:

book-form.jsp Renders form to update or add a Book

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Book Form</title>
</head>

<body>

	<h1>Book Form</h1>


	<form method="post"
		action="${pageContext.request.contextPath}/book">
		<fieldset>
			<legend>
				<c:choose>
					<c:when test="${not empty book.id }">
						Updating Book
					</c:when>
					<c:otherwise>
						Adding Book
					</c:otherwise>
				</c:choose>
			</legend>

			<div>
				<label for="title">Title</label> <input type="text" name="title"
					id="title" value="${book.title}" />
			</div>

			<div>
				<label for="description">Description</label>
				<textarea name="description" id="description" rows="2" cols="60">${book.description}</textarea>
			</div>

			<div>
				<label for="price">Price $</label> <input name="price" id="price" 
					value="${book.price}" />
			</div>

			<div>
				<label for="pubDate">Publication Date</label> 
				<input name="pubDate" id="pubDate" 
					value="${bookPubDate}" />
					<label class="after">(MM/DD/YYYY)</label>
			</div>

			<c:if test="${not empty book.id}">
				<input type="hidden" name="id" value="${book.id}" />
			</c:if>

		</fieldset>

		<div class="button-row">
			<a href="${pageContext.request.contextPath}/book/">Cancel</a> or <input type="submit" value="Submit" />
		</div>
	</form>

</body>
</html>

The book-form.jsp uses JSTL c:choose, c:otherwise to display whether we are adding a new book or updating a book as follows:

book-form.jsp using JSTL c:choose to display update or add status

...
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
...
			<legend>
				<c:choose>
					<c:when test="${not empty book.id }">
						Updating Book
					</c:when>
					<c:otherwise>
						Adding Book
					</c:otherwise>
				</c:choose>
			</legend>

...

If the book.id property is present the test "Updating Book" is rendered.

Also, the form renders a hidden id property if it is doing an edit/update operation as follows:

book-form.jsp using JSTL c:if to hidden id field for edit/update operation

			<c:if test="${not empty book.id}">
				<input type="hidden" name="id" value="${book.id}" />
			</c:if>

The doPost method of BookEditorServlet handles the form submission as follows:

Creating a doPost method to handle the form submission

BookEditorServlet.java doPost

package com.bookstore.web;

...

@WebServlet("/book")
public class BookEditorServlet extends HttpServlet {

	@Inject
	private BookRepository bookRepo;
		
	
    ...
    /**
	 * Handles posting an HTML book form. If the id is null then it is an
	 * "add book", if the id is set then it is an "update book"
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		String title = request.getParameter("title");
		String description = request.getParameter("description");
		String price = request.getParameter("price");
		String pubDate = request.getParameter("pubDate");
		

		String id = request.getParameter("id");
		if (id == null || id.isEmpty()) {
			bookRepo.addBook(title, description, price, pubDate);
		} else {
			bookRepo.updateBook(id, title, description, price, pubDate);
		}

		response.sendRedirect(request.getContextPath() + "/book/");
	}
    ...
}

Notice that if the id is null then BookEditorServlet.doPost calls bookRepo.addBook, otherwise it calls bookRepo.updateBook. Once the form handling is done, doPost redirects to /book/. A redirect means an extra hit to the server, since we are basically telling the browser to load another link. The astute reader may wonder why we don't just do a forward like we did in the doGet. The answer is bookmarking. The URL /book/ (ending in slash) represents a collection of books, while /book (no slash) represents a single book (using REST style URL). If we did a forward, then after the form submission the browser would show the listing, but under the URI /book instead of /book/. If the ender user linked to /book, it would not take them back to the listing but back to a form, which not correct. This is why we do a sendRedirect instead of a forward.

Now we have an add/edit/update/read/ and listing. It is everything you could want from a CRUD listing.

At this point, we don't do much validation. We will add this in a later lesson.

Quick review of what we have so far

We have created the following files for this bookstore example:

$ find .
.
./WebContent/WEB-INF/pages/book-form.jsp           Book Form
./WebContent/WEB-INF/pages/book-list.jsp              Book Listing
./src/META-INF/beans.xml                                        Needed for Java EE dependency injection (CDI)
./src/com/bookstore/Book.java                                 Domain/model object
./src/com/bookstore/BookRepositoryImpl.java          Repository implementation using Java collections (just for testing)
./src/com/bookstore/BookRepository.java                 Interface to Book Repository so we can swap it out with JDBC, JPA, JCache and MongoDB version later 
./src/com/bookstore/web/BookEditorServlet.java      Servlet that loads Book (doGet) form and handles Book form submissions (doPost).
./src/com/bookstore/web/BookListServlet.java          Servlet that looks up a list of books and displays the listing

./WebContent/WEB-INF/pages/book-form.jsp full listing

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE HTML>
<html>
<head>
<title>Book Form</title>
</head>

<body>

	<h1>Book Form</h1>


	<form method="post"
		action="${pageContext.request.contextPath}/book">
		<fieldset>
			<legend>
				<c:choose>
					<c:when test="${not empty book.id }">
						Updating Book
					</c:when>
					<c:otherwise>
						Adding Book
					</c:otherwise>
				</c:choose>
			</legend>

			<div>
				<label for="title">Title</label> <input type="text" name="title"
					id="title" value="${book.title}" />
			</div>

			<div>
				<label for="description">Description</label>
				<textarea name="description" id="description" rows="2" cols="60">${book.description}</textarea>
			</div>

			<div>
				<label for="price">Price $</label> <input name="price" id="price" 
					value="${book.price}" />
			</div>

			<div>
				<label for="pubDate">Publication Date</label> 
				<input name="pubDate" id="pubDate" 
					value="${bookPubDate}" />
					<label class="after">(MM/DD/YYYY)</label>
			</div>

			<c:if test="${not empty book.id}">
				<input type="hidden" name="id" value="${book.id}" />
			</c:if>

		</fieldset>

		<div class="button-row">
			<a href="${pageContext.request.contextPath}/book/">Cancel</a> or <input type="submit" value="Submit" />
		</div>
	</form>

</body>
</html>


./WebContent/WEB-INF/pages/book-list.jsp full listing

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix ="c" %>
<!DOCTYPE HTML>

<html>
<head>
<title>Book listing</title>
</head>
<body>

<a href="${pageContext.request.contextPath}/book">Add Book</a>

<table>
	<tr>
		<th>Title</th>
		<th>Description</th>
		<th>Price</th>
		<th>Publication Date</th>
	</tr>
	
	<c:forEach var="book" items="${books}">
		<tr>
			<td><a href="${pageContext.request.contextPath}/book?id=${book.id}">${book.title}</a></td>
			<td>${book.description}</td>
			<td>${book.price}</td>
			<td>${book.pubDate}</td>
		</tr>
	</c:forEach>
</table>

</body>
</html>


./src/META-INF/beans.xml full listing

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

./src/com/bookstore/Book.java

package com.bookstore;

import java.math.BigDecimal;
import java.util.Date;

public class Book implements Cloneable {

	private String title; 
	private String description;
	private BigDecimal price;
	private Date pubDate;
	private String id;
	
	public Book(String id, String title, String description, BigDecimal price, Date pubDate) {
		this.id = id;
		this.title = title;
		this.description = description;
		this.price = price;
		this.pubDate = pubDate;
	}
	
	public Book () {
		
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public BigDecimal getPrice() {
		return price;
	}

	public void setPrice(BigDecimal price) {
		this.price = price;
	}

	public Date getPubDate() {
		return pubDate;
	}

	public void setPubDate(Date pubDate) {
		this.pubDate = pubDate;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}
	
	public Book cloneMe()  {
		try {
			return (Book) super.clone();
		} catch (CloneNotSupportedException e) {
			return null;  
		}
	}
	
	@Override
	public String toString() {
		return "Book [title=" + title + ", description=" + description
				+ ", price=" + price + ", pubDate=" + pubDate + ", id=" + id
				+ "]";
	}

}


./src/com/bookstore/BookRepositoryImpl.java full listing (testing only)

package com.bookstore;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.math.BigDecimal;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class BookRepositoryImpl implements BookRepository {

	private SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
	private int count;
	private Map<String, Book> idToBookMap = new HashMap<String, Book>();

	public BookRepositoryImpl()  {
		synchronized (this) {
			books(book("War and Peace", "blah blah blah", "5.50", "5/29/1970"),
					book("Pride and Prejudice", "blah blah blah", "5.50", "5/29/1960"),
					book("book1", "blah blah blah", "5.50", "5/29/1960"),
					book("book2", "blah blah blah", "5.50", "5/29/1960"),
					book("book3", "blah blah blah", "5.50", "5/29/1960"),
					book("book4", "blah blah blah", "5.50", "5/29/1960"),
					book("book5", "blah blah blah", "5.50", "5/29/1960"),
					book("book6", "blah blah blah", "5.50", "5/29/1960"),
					book("book7", "blah blah blah", "5.50", "5/29/1960"),
					book("book8", "blah blah blah", "5.50", "5/29/1960"),
					book("book9", "blah blah blah", "5.50", "5/29/1960"),
					book("Java for dummies", "blah blah blah", "1.99", "5/29/1960"));
		}
	}

	private Book book(String title, String description, String aPrice,
			String aPubDate)  {

		Date pubDate = null;
		BigDecimal price = null;
		
		try {
			price = new BigDecimal(aPrice);
		}catch (Exception ex) {
		}
		
		try {
			pubDate = dateFormat.parse(aPubDate);
		}catch (Exception ex) {
		}
		
		return new Book("" + (count++), title, description, price, pubDate);
		
	}

	private void books(Book... books) {
		for (Book book : books) {
			doAddBook(book);
		}
	}

	private void doAddBook(Book book) {
		synchronized (this) {
			this.idToBookMap.put(book.getId(), book);
		}
	}

	@Override
	public Book lookupBookById(String id)  {
		synchronized (this) {
			return this.idToBookMap.get(id).cloneMe();
		}
	}

	@Override
	public void addBook(String title, String description, String price,
			String pubDate)  {
		doAddBook(book(title, description, price, pubDate));
	}

	@Override
	public void updateBook(String id, String title, String description,
			String price, String pubDate) {
		Book book = book(title, description, price, pubDate);
		synchronized (this) {
			book.setId(id);
			this.idToBookMap.put(id, book);
		}
	}

	private List<Book> doListBooks()  {
		List<Book> books;
		synchronized (this) {

			books = new ArrayList<Book>(this.idToBookMap.size());
			for (Book book : this.idToBookMap.values()) {
				books.add(book.cloneMe());
			}
		}
		return books;
	}
	
	public List<Book> listBooks() {
		
		List<Book> books = doListBooks();

		Collections.sort(books, new Comparator<Book>() {
			public int compare(Book bookA, Book bookB) {
				return bookA.getId().compareTo(bookB.getId());
			}
		});
		return books;
	}

	@Override
	public void removeBook(String id)  {
		synchronized(this) {
			this.idToBookMap.remove(id);
		}
	}

}

./src/com/bookstore/BookRepository.java full listing

package com.bookstore;

import java.util.List;

public interface BookRepository {
	Book lookupBookById(String id);

	void addBook(String title, String description,
			String price, String pubDate);

	void updateBook(String id, String title,
			String description, String price, String pubDate);
	
	void removeBook(String id);


	List<Book> listBooks();

}

./src/com/bookstore/web/BookEditorServlet.java

package com.bookstore.web;

import java.io.IOException;
import java.text.SimpleDateFormat;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.bookstore.Book;
import com.bookstore.BookRepository;

/**
 * Servlet implementation class BookEditorServlet
 */
@WebServlet("/book")
public class BookEditorServlet extends HttpServlet {

	@Inject
	private BookRepository bookRepo;
		
	private SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
	
	/** Prepare the book form before we display it. */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		String id = request.getParameter("id");

		if (id != null && !id.isEmpty()) {
			Book book = bookRepo.lookupBookById(id);
			request.setAttribute("book", book);
			request.setAttribute("bookPubDate", dateFormat.format(book.getPubDate()));
		}

		/* Redirect to book-form. */
		getServletContext().getRequestDispatcher("/WEB-INF/pages/book-form.jsp").forward(
				request, response);

	}
	
    /**
	 * Handles posting an HTML book form. If the id is null then it is an
	 * "add book", if the id is set then it is an "update book"
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {

		String title = request.getParameter("title");
		String description = request.getParameter("description");
		String price = request.getParameter("price");
		String pubDate = request.getParameter("pubDate");
		

		String id = request.getParameter("id");
		if (id == null || id.isEmpty()) {
			bookRepo.addBook(title, description, price, pubDate);
		} else {
			bookRepo.updateBook(id, title, description, price, pubDate);
		}

		response.sendRedirect(request.getContextPath() + "/book/");
	}
}

./src/com/bookstore/web/BookListServlet.java

package com.bookstore.web;

import java.io.IOException;

import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.bookstore.BookRepository;

@WebServlet("/book/")
public class BookListServlet extends HttpServlet {
	
	@Inject 
	private BookRepository bookRepo;

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setAttribute("books", bookRepo.listBooks());
		getServletContext().getRequestDispatcher("/WEB-INF/pages/book-list.jsp").forward(request, response);
	}

}

Technical debt

We have accumulated some technical debt. Our UI is just plain HTML, it has no styling. This is probably and oversight. There is no footer, or header. There probably should be some sort of header area that has a link to the home page, and a footer with a copyright notice or something. The book listing does not format the price of the date, it should.

We will address these issues and others in the coming lessons.

Cookbooks and Tutorials

Personal tools
TOOLBOX
LANGUAGES