SummaryBy Dario Laverde note: last updated 2/29/04 - also see Back to Basics Part 2 (includes unit testing)
A review of the basics in web development with Java using Tomcat as the web server / servlet /JSP engine and Eclipse as the development environment. For those who are new to Java development, a simple JSP and servlet are shown using a basic user login form and JDBC to access a database. In this example we'll be using MySQL as the database although any database with the appropriate JDBC driver can be used. For the seasoned developer, how to effectively use the Eclipse environment to develop and debug will be illustrated including techniques in running Tomcat inside the debugger as well as using an Eclipse plugin, JFaceDbc, to manage and debug the database using an SQL front end from within the Eclipse IDE. (January, 2004)
![]()
o
perhaps you're intimidated by J2EE and would like to start from the
basics. Or perhaps you'd like to revisit the basics to quickly develop
a solution for a simple problem without using J2EE or web frameworks
that may be overkill for the problem domain. Or perhaps you've been
reading books like Bitter EJB or studying for your web components
certification or maybe just a bit
glassy-eyed by all the new J2EE 1.4 specs and frameworks out
there. Well here's a back to basics example problem, a
simple web based user login with as simple as you can get
servlet/JSP/JDBC solution. But for the advanced developers, I'll show
you how to package it into a deployable war file for Tomcat
5.0 and in addition I'll show you how to debug with Tomcat running
within
Eclipse and demonstrate a database utility plugin for Eclipse called
JFaceDbc, which is not to be confused with JSF (Java Server Faces).
In this example we
will not use an 'MVC
framework' such as Struts or its successor Java
Server Faces (JSF). In fact we won't even use a controller servlet. The
point here is to illustrate the simplest example of web based database
access
that you will be able to reproduce from memory at your next job
interview (should you need to prove the basics)
Here's what we'll
cover:
The HTML form
We'll start out with the form as an html page and later save it as a
JSP page. As forms get more complicated one has to resort to an HTML
editor and not all editors recognize JSP syntax and/or the .jsp
extension. So
adding the JSP elements and renaming the file is done manually after
using the html editor to create the form (unless one has access to
a GUI JSP page editor)
<html>
<body>
<form method="post" name="login" action="login">
Username: <input type="text" name="username"><br>
Password: <input type="password" name="password"><br>
<input type="submit" value="log in">
</from>
</body>
</html>
For now this is just an index.html page we'll add the JSP element below.
The
Servlet
Example 1. Here's the
entire web app essentially:
package org.nycjava;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.sql.*;
import java.sql.*;
import javax.naming.*;
import java.io.*;
public class Login extends HttpServlet {
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException {
Context initCtx=null;
Context envCtx=null;
DataSource dataSource=null;
Connection conn=null;
Statement stmt=null;
ResultSet rs=null;
// obtain the html form's
parameters
String
username=request.getParameter("username");
String
password=request.getParameter("password");
// the error message to
display to client
String errmsg="";
// Here's the JNDI lookup to
obtain our database source
try {
initCtx = new InitialContext();
envCtx= (Context)initCtx.lookup("java:comp/env");
dataSource= (DataSource)envCtx.lookup("jdbc/login");
// in general we'll either
handle known errors or pass them up asServletExceptions
} catch(NamingException ne) {
System.err.println("db naming error: "+ne.getMessage());
return;
} catch(Exception e) {
throw new ServletException(e.getMessage());
}
// obtain a db connection
from the connection pool provided by Tomcat's datasource
try {
conn
= dataSource.getConnection();
} catch (SQLException e) {
System.err.println("db sql error: "+e.getMessage());
} catch(Exception e) {
throw new ServletException(e.getMessage());
}
// perfrom SQL query:
try {
stmt
= conn.createStatement();
StringBuffer cmd=
new StringBuffer("select * from users where
username='");
cmd.append(username);
cmd.append("'");
rs =
stmt.executeQuery(cmd.toString());
if(
rs.next() ) {
if(!rs.getString("pwd").equals(password)) {
errmsg="wrong password";
}
} else {
errmsg="user not found";
}
} catch (SQLException
sqle) {
System.err.println(sqle.getMessage());
} catch (Exception e) {
throw new ServletException(e.getMessage());
// always close and set
references to null to return connection to pool
} finally {
if
(rs != null) {
try {
rs.close();
} catch (SQLException sqle) {
System.err.println(sqle.getMessage());
}
rs = null;
}
if
(stmt != null) {
try {
stmt.close();
} catch (SQLException sqle) {
System.err.println(sqle.getMessage());
}
stmt = null;
}
if
(conn != null) {
try {
conn.close();
} catch (SQLException sqle) {
System.err.println(sqle.getMessage());
}
conn = null;
}
}
if(errmsg.length()>0) {
HttpSession session = request.getSession();
session.setAttribute("errmsg",errmsg);
forward(request, response, "/index.jsp");
} else {
response.setContentType("text/html");
try
{
PrintWriter out = response.getWriter();
out.println("<html><body>Success!</body></html>");
out.close();
}
catch (IOException e) {
throw new ServletException(e.getMessage());
}
}
}
// in case someone accesses
this servlet with a 'get' simply redirect to form
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException {
forward(request,response,"/index.jsp");
}
// convenience method to forward requests
private void forward(HttpServletRequest request,
HttpServletResponse response,
String target) throws ServletException {
try {
RequestDispatcher disp=
getServletContext().getRequestDispatcher(target);
disp.forward(request, response);
} catch(IOException ioe) {
throw new ServletException(ioe.getMessage());
}
}
}
The
database connection
uses the underlying servlet container's (Tomcat's) datasource which
includes a connection pool. Notice how one has to make sure the
connection is freed (nulled) in order to a avoid a pool 'leak'. An
HttpSession is used to set a result attribute (an appropriate error
message) that we will pass to the JSP page which just so happens to be
the input form page.
At this point we wil
add the JSP tag to
display the attribute we set in the servlet and place the error message
right before the form and rename the page to index.jsp. Upon success, we
simply
write out a "success" web page although we could have also forwarded
the
response to another jsp page. Below is the revised form.
<html>
<body>
<% String
msg=(String)session.getAttribute("errmsg"); if(msg!=null)
out.println(msg); %>
<form method="post" name="login" action="login">
Username: <input type="text"
name="username"><br>
Password: <input type="password"
name="password"><br>
<input type="submit" value="log in">
</from>
<script language="JavaScript">
<!--
document.login.username.focus()
//-->
</script>
</body>
</html>
If
we had simply aded <%=
session.getAttribute("errmsg") %> it would
have displayed "null" when you first accessed the page, hence the check
for null. For completion sake we also add the JavaScript necessary to
add the default focus which every good form should have.
Note that Java code
in a JSP page is not desirable hence the need for custom tags and
JSTL, but we're back to basics for now.
The
Web Application
The file hierarchy for the web application is as follows:
What looks new
here to seasoned developers is
the context.html file which is where the context and database
connection is defined
for our application in Tomcat where previously we had to
edit the global server.xml configuration file. Again, the web.xml
file is where we define and configure servlets and references to the
resources for our web application.
web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web
Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>login</display-name>
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>org.nycjava.Login</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<resource-ref>
<description>DB
Connection</description>
<res-ref-name>jdbc/login</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<!-- used to force use of SSL (https)
<security-constraint>
<web-resource-collection>
<web-resource-name>entire
application</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
-->
</web-app>
context.xml
<Context path="/login" docBase="login" debug="0" reloadable="false"
crossContext="true">
<Resource name="jdbc/login" auth="Container"
type="javax.sql.DataSource"/>
<ResourceParams name="jdbc/login">
<parameter>
<name>factory</name>
<value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
</parameter>
<parameter>
<name>maxActive</name>
<value>100</value>
</parameter>
<parameter>
<name>maxIdle</name>
<value>30</value>
</parameter>
<parameter>
<name>maxWait</name>
<value>10000</value>
</parameter>
<parameter>
<name>username</name>
<value>login</value>
</parameter>
<parameter>
<name>password</name>
<value>password</value>
</parameter>
<parameter>
<name>driverClassName</name>
<value>com.mysql.jdbc.Driver</value>
</parameter>
<parameter>
<name>url</name>
<value>jdbc:mysql://localhost/login?autoReconnect=true</value>
</parameter>
</ResourceParams>
</Context>
Using
MySQL and Eclipse
Create a project that wil set the output folder to WEB-INF/classes and
add the external servlet-api.jar from the Tomcat lib folder to the
project's classpath. Your servlet should compile without errors.
Note: For the standard Tomcat
distribution it's the common/lib folder that I refer to when I mention Tomcat's lib folder. In this
specific example I'm using the embedded distribution since there are less jar files and the distribution is
much smaller and minimal (there's only one lib folder). If you do decide to use this embedded
distribution you must add the commons-dbcp and commons-pool jar files
to the lib folder to provide connection pooling (available online from http://jakarta.apache.org/commons
or you can copy it from the full Tomcat distribution).
Creating the Database:
First, if you haven't already done so, start the MySQL server (bin/mysqld or bin/mysqladmin or bin/winmysqladmin). Also make sure you have the MySQL JDBC driver jar file installed in your Tomcat lib folder (e.g. mysql-connector-java-3.0.11-stable-bin.jar)
If you wish to use
the command line instead of Eclipse, use the bin/mysql client to access
the main database and if you haven't already, assign a password to root
(it's blank by default). Run the SQL scripts (including with souirce
code) in one of two ways; log into the MySQL server using the mysql
client and use the source command (source [script file]) -or- redirect
the script into the mysql client command:mysql -u[uername] -p[password] [database] < script.sql
For Eclipse, install
the
JFaceDbc plugin from http://jfacedbc.sf.net
and you will have a new
Eclipse Perspective (select it from the perspective toolbar, the left
side tool bar). You can also open the perspective from the Window menu: Open Perspective->Other...
Find the Drivers pane (one of the tabbed panes in the upper left) and locate the MySQL driver named MMMySQL (Mark Matthews wrote it originally but now it's part of mysql.com) Set the location of the driver jar file, which you should have copied into your Tomcat lib folder Enter the driver class name as com.mysql.jdbc.Driver. (I also renamed the driver to MySQL as shown below).
|
Figure 1. |
At this point
you
can now create an alias called "mysql" to access the database server
from the aliases pane..
Then from the Connections pane (lower left) you will create a new
connection and login as root in
order to create a new database and a new user
for that database. Select the correct alias for the database you are
using and create a new connection to it. You can then create a new
SQL editor window to run SQL commands (right-click on the connection)
Enter the commands shown (or use script file available with source code) to create the database and the user.
create database
login;
grant all privileges on login.* to "login"@"localhost" identified by
"password"; (note: on linux you may have
to use
localhost.localdomain)
We can then close
this connection and create a new alias called login
that will use the new user and new database that we just created. Again
create a new connection and open an SQL editor where we can create the
table and populate the first
entry either by typing in the following or using an external .sql file.
Also available with the plugin is the ability to interactively edit the
tables and data from database structure view pane (bottom pane) by
selecting a table and right-clicking on it.
use login;
create table if not exists users (
id int auto_increment primary key,
username varchar(100),
pwd varchar(100),
role int
);
insert into users values(null,'admin','password',1);
From
the main Eclipse menu, File->New->Other... we can create a schema
ERD diagram by simply
dragging the table(s) we want from the database structure view.
Deploying to Tomcat:
Switch back to the Java
perspective (some of the JFaceDbc views will remain but you can close
them if you wish).
Copy the javac compiler from your JDK lib (the tools.jar
file) to your Tomcat lib folder. This is needed for Tomcat to compile
JSPs. Alternatively you can use IBM's Jikes compiler. (see Tomcat's global web.xml for how to use Jikes)
Running Tomcat inside Eclipse:
Create a new Run
configuration and set the following:
Main Class:
org.apache.catalina.startup.Bootstrap
Program Arguments: start
VM Arguments:
-Dcatalina.home=/usr/jakarta-tomcat-5.0.16-embed (or where your
TOMCAT_HOME is)
Classpath - User Entries:
add as external jar files all of Tomcat's jar files found in the lib
folder(s).
Note that for the standard distribution these
are: common/lib, server/lib, and bin For the embedded
distribution it's just the lib folder.
Go ahead and run
Tomcat. Our initial deployment will occur on a live Tomcat server. For
the embedded distribution, you will have to
create a server.xml file under conf and set the standalone web server
port. An example is found here.
Create and deploy the war file:
Select your login
project in the Java perspective, right click and select Export... Now
select jar file and from the
Jar export window select the files and
folders you wish to include (the hierarchy noted above). Now browse to
the Tomcat webapps
folder and save it as login.war To create the war file manually use the jar command:
jar cvf [tomcat /webapps]/login.war META-INF WEB-INF index.jsp
At this point you
have just deployed your application and you can test it with a web
browser. e.g. http://localhost:8080/login
Developing without deploying everytime:
Here's an
interesting and not quite documented sidebar:
| When Tomcat deploys
your application and it has a context.xml file in the META-INF folder,
it will copy and rename the file to login.xml and place it in the /conf/Catalina/localhost folder. From then on it will use this as the context file for your web application. |
To run and debug
Tomcat while using your project files in your current working or
development folder, simply edit that file and point the docBase to your
development folder.
For example simply
edit the first line of [tomcat_home]/conf/Catalina/localhost/login.xml
Context path="/login" docBase="c:\myprojects\login"
debug="0" reloadable="false" crossContext="true">
Restart Tomcat and
from now it will use your development folder instead of what's in
webapps. In fact to avoid confusion, delete both the login.war file and
the expanded folder in webapps. Now you can make changes to your
application and have the changes reflected immediately while debugging.
optionally you may want to install Tomcat's source code so that you can
step into the web application server code as well.
Summary
Hopefully this will server as a re-introduction to those who are still
fairly new to
Java web development and as a refresher course with tips on how to be
more productive for seasoned developers. Sometimes it's necessary to go
back to basics every now and then to remind us what lies underneath all
those frameworks and tools. A simple problem usually deserves a
simple solution and not one that may be overkill. It can also serve to
benchmark performance and complexity relative to using frameworks as
the problem domain expands. Once one masters the basics one can very
quickly develop prototypes before moving on to EJBs.
Recommended Exercises: