99
A Comparative Study of Performance for J2EE Data Access Technologies by Dimitar Misev Submitted in partial fulfillment of the requirements for the degree of M.S. in Computer Science at School of Computer Science and Information Systems Pace University May 2006

A Comparative Study of Performance for J2EE Data …support.csis.pace.edu/CSISWeb/docs/MSThesis/MisevDimitar.pdf · A Comparative Study of Performance for J2EE Data Access Technologies

  • Upload
    dodan

  • View
    216

  • Download
    0

Embed Size (px)

Citation preview

A Comparative Study of Performance for J2EE Data Access Technologies

byDimitar Misev

Submitted in partial fulfillmentof the requirements for the degree of

M.S. in Computer Science

at

School of Computer Science and Information Systems

Pace University

May 2006

We hereby certify that this dissertation, submitted by Dimitar Misev, satisfies the dissertation requirements for the degree of M.S. in Computer Science and has been approved.

_____________________________________________-________________Dr. Lixin Tao DateChairperson of Dissertation Committee

_____________________________________________-________________Dr. Narayan Murthy, Chair, CS Dept. DateDissertation Committee Member

_____________________________________________-________________Dr. Mehdi Badii DateDissertation Committee Member

_____________________________________________-________________Dr. Susan M. Merritt DateDean, CSIS

School of Computer Science and Information SystemsPace University 2006

Abstract

A Comparative Study of Performance for J2EE Data Access Technologies

byDimitar Misev

Submitted in partial fulfillmentof the requirements for the degree of

M.S. in Computer Science

May 2006

Most businesses today are at the reach of their customers through Internet and millions of online transactions take place every day. Therefore, choosing the right technology for doing business online certainly is important. And even more, speed is a key factor in most cases.

Two key players in the field today are Microsoft and Sun Microsystems. Microsoft has its .NET platform, which supports languages like Visual Basic .NET and C# .NET. Anything written for this platform can only run on a Windows operating system. On the other hand, Sun Microsystems has its Java technology, which is much more portable and can run on any operating system.

Part of Java, known as J2EE (Java 2 Platform Enterprise Edition) server technology, emerged as separate discipline meant to satisfy business needs. This technology providesa solid backbone for building reliable business applications that meet rigorous criteria.

With speed being a key factor in most businesses offering their services on the Internet, it is crucial for a J2EE application to be able to handle a certain maximum number of users. In most the cases there is a relational database involved in the application, regardless if it is reading, writing or updating data. Usually this is the bottleneck of an application (the slowest part), and its speed reflects overall systemperformance. Choosing the right approach to work with a database can make a difference and that is the object of research in this thesis. A simple test application that uses a number of different data access technologies will be implemented. This application will then be used in tests against real data.

Using the results from these experiments, conclusions will be given along with some rules on choosing the right data access technology for the next big J2EE project.

Acknowledgements

I would like to express my sincere gratitude to my advisor, Dr. Lixin Tao. Throughout my thesis, he provided me with valuable guidance and help. He always found time for me in the middle of his busy schedule to go over the details and provide suggestions and direction. Without his help and constructive criticism, I cannot imagine completing my thesis. Whenever I was stuck and needed direction, he was there for me to guide me through.

I would like to thank the department Chairperson, Dr. Narayan Murthy, for giving me the opportunity to do my thesis and his review and comments.

I would also like to thank my research committee member, Dr. Mehdi Badii for his guidance and support.

The project wouldn’t have been completed without Tom Lombardi, our technician who setup the Blade Server.

Finally, I would like to thank IBM for their Academic Initiative program which provided the main part of software and hardware used in the thesis.

v

Table of Contents

Abstract .............................................................................................................................. iii

List of Tables .................................................................................................................... vii

List of Figures .................................................................................................................. viii

Chapter 1 Introduction................................................................................................. 1

1.1 Background..................................................................................................... 1

1.1.1 Importance of database access efficiency ................................................... 3

1.1.2 Alternative data access technologies .......................................................... 6

1.2 Problem Statement .......................................................................................... 7

1.3 Solution Strategy............................................................................................. 8

1.4 Thesis roadmap ............................................................................................... 8

Chapter 2 J2EE Data Access Technologies............................................................... 10

2.1 JDBC based data access................................................................................ 12

2.2 Stored procedures.......................................................................................... 14

2.3 EJB (Entity Java Beans)................................................................................ 16

2.4 Spring Framework ........................................................................................ 20

Chapter 3 Benchmark Application ............................................................................ 24

3.1 Design ........................................................................................................... 24

3.2 Implementation ............................................................................................. 26

Chapter 4 Experimental Study of Data Access Performance .................................... 44

4.1 Experimental environment............................................................................ 44

4.2 Testing process and results ........................................................................... 44

Chapter 5 Conclusion ................................................................................................ 57

Appendix A Benchmark Application Installation Guide................................................ 59

Creating database infrastructure ................................................................................... 59

Setting up server resources ........................................................................................... 60

vi

Application deployment & generating test data............................................................ 62

Setting up the project with RAD................................................................................... 63

Software tools used for the application......................................................................... 65

The hardware behind the code ...................................................................................... 66

Appendix B Key Source Files......................................................................................... 66

References......................................................................................................................... 90

vii

List of Tables

Table 1 - Experiment Data................................................................................................ 46

viii

List of Figures

Figure 1 J2EE application server bottleneck ...................................................................... 4

Figure 2 Life Cycle of an Entity Bean .............................................................................. 17

Figure 3 Overview of the Spring Framework ................................................................... 21

Figure 4 Benchmark application flow............................................................................... 25

Figure 5 Web Application Structure ................................................................................. 28

Figure 6 - Result Data Graph ............................................................................................ 53

1

Chapter 1

Introduction

1.1 Background

Being a free, open source technology that runs on every platform, J2EE (Java 2

Platform Enterprise Edition) server technology is becoming an industry standard in the

enterprise world. J2EE has proven successful in providing scalable, robust, portable and

reliable server-side Java applications that businesses need.

It is based on J2SE (Java 2 Platform Standard Edition) and in fact many libraries are

simply borrowed from there. J2EE provides infrastructure for easier and faster

development of distributed applications, and well-established standards for their

distribution and deployment. With its set of reusable components, transaction control,

unified security and web services support (which is the hottest new technology now) it is

very attractive to application developers, letting them focus on the business logic rather

than the underlying components and thus reducing development time.

The fact that it is free makes it appealing to many companies. Written in Java, it runs

regardless of the underlying platform and operating system, also known as the “write

once, run everywhere” motto. Any system, from cell phones and PDA devices, to super

computers, can run Java applications as long as it has a JVM (Java Virtual Machine)

2

installed. That way the applications written in Java are not limited to a specific vendor or

platform.

A typical J2EE application runs in a container or application server, such as Tomcat

(which is not a full J2EE server, but a servlet container), JBoss, WebSphere or

WebLogic, among the others. Some of these are free, while others are not. Some provide

custom features that others might not implement. All of them conform to the J2EE

specification, making it easy to deploy a J2EE application written for JBoss to a

WebSphere application server. Usually a few changes in the configuration files are

sufficient to migrate an application from one to another type of server, unless of course

the application uses some custom feature that the other application server does not have.

The same flexibility applies when choosing the operating system in which a J2EE

application server runs applications. It is completely up to the system engineer to decide

whether the application will run on a Linux or Windows OS, or something else.

In order to simplify the implementation of different vendor technologies in a J2EE

server application, J2EE specifies a connector architecture that allows using third party

systems, such as various RDBMS products (Relational Database Management Systems)

for example. In terms of data access infrastructure, a typical J2EE application server

provides everything from transaction control to resource pooling and security.

In general, J2EE server technology follows a set of well-specified, established and

accepted rules. By using it, developers can focus on the business rules of an application,

rather than the implementation details underneath.

3

1.1.1 Importance of database access efficiency

Most often, applications deal with data in one or another way. Data can reside in the

physical memory while the server is up, but it has to be stored somewhere else before the

server goes down, otherwise it will be lost permanently. One way to keep data

permanently is by storing it into a database. This is where database access efficiency

comes into play.

J2EE application servers provide scalable performance for a wide range of

applications. However, the general-purpose nature of J2EE, which aims to address the

needs of every enterprise, also limits its ability to provide a best-of-breed solution for

mission-critical applications. In particular, data-intensive applications expose a serious

data bottleneck in all J2EE server architectures.

A recent survey of 360 J2EE users found that 57 percent of application performance

and availability issues can be traced to inefficient data access problems, and only 42

percent of applications perform as planned during initial deployment. Not surprisingly,

the survey went on to state that Java applications fail to meet user expectations 60 percent

of the time. Worse yet, a 2004 survey conducted by Forrester Research found that more

than two-thirds of respondents discovered application performance problems only when a

user called the help desk.

Typically, J2EE servers convert every request for persistent data into one or more

SQL statements. For applications with complex object models and heavy request

volumes, this approach creates inevitable problems, as illustrated in Figure 1 J2EE

application server bottleneck.

4

Figure 1 J2EE application server bottleneck

The two application characteristics that most often contribute to data bottlenecks are:

1. The number of data objects, which drives the complexity of the object-relational

mapping (mapping the entity bean to a persistent store)

2. The peak transaction rate, which drives the volume of database requests

As the size of the application object model grows, the difficulty of defining an

efficient object-relational mapping increases. Highly efficient mapping is necessary to

prevent mapping bottlenecks. As the application request volume grows, the database will

become a bottleneck based on the sheer volume of database queries. Intelligent caching

can prevent query bottlenecks. For applications with both complex object models and

high request volumes, eliminating data bottlenecks requires a more systematic approach.

5

A good rule of thumb for predicting data bottlenecks is the 50/50 rule. J2EE

applications that have more than 50 data classes and/or more than 50 transactions per

second during peak times are much more likely to experience serious data bottlenecks.

A number of approaches exist to prevent data bottlenecks. Some of them are listed

here:

Use lazy loading: A common mistake is to fetch entire object hierarchies from the

database when an object slice or hierarchy node is sufficient for the application's

needs. Loading data only when absolutely needed is one way to reduce the overall

database traffic.

Database replication: Replicating the database is one way to break up a flood of

requests so they can be managed more efficiently. This also provides an effective

way for boosting performance in a particular region. However, this can be an

expensive approach, requiring additional servers and database licenses.

Object caching within a J2EE server: Caching frequently used objects in-memory

within the J2EE server reduces the database's load and improves response time.

Some J2EE servers include limited caching for CMP (container-managed

persistence) beans; however, this may not adequately address the performance

issues, as described later in this article.

Database operations are typically slow and are the bottleneck of every application.

Database read, write and update are all IO operations, and it is important that they are as

fast as possible, because they affect overall application performance. In fact, it is best to

avoid database operations unless it is necessary. Various cashing techniques exist to

6

achieve this. This can be helpful in some situations, like for example, if the server

application needs to display data that rarely changes. Instead of letting each client get

data directly from the server database, it would be a lot faster to get the data from

database once and keep it in memory for successive requests.

However, in complex applications where data rapidly changes, cashing data is not

very helpful. Even more, these operations usually happen in bursts. This means that most

of the time there is no interaction with the database, but there are short intervals when

large data transfers occur. In these cases, it is extremely important that database access is

fast.

1.1.2 Alternative data access technologies

There is more than one database access API to choose from when developing a J2EE

application. The ones that are in common use today satisfy wide range of requirements.

Some applications use data that does not change that often, but need to maintain complex

relationships, and be flexible enough to change fast. Others need real time values that

change to the second. Another kind of application might need to stream media files from

a database. And then, there are applications that need to leverage transaction control, like

banking systems for instance. In case of funds transfer from one account to another, there

are two consequtive database interactions: one that deducts the amount from the first

account, and another one that adds it to the second account. In case the second operation

fails, someone’s money will be lost by the system, which shouldn’t happen by any

means. This situation can be avoided by using transaction control. If one operation fails,

all of them are rolled back, leaving data unchanged. Traditional JDBC transaction control

7

only works for database operations. Most data access technologies provide basic JDBC

transaction control mechanism. There are situations when this is not enough.

If the application needs to send a JMS (Java Messaging System) message and

perform a database operation in a single transaction, JDBC transaction control can’t help

with this, because JMS is out of it’s scope. In this situation either the Spring API can be

used with it’s declarative transaction support or an EJB container transaction support is

needed. Furthermore, a specific 2-PC (2 Phase Commit) compliant database driver needs

to be used to connect to the database.

Another important issue with data access technologies is concurency, or how

simultaneous access is handled. There are a couple of different transaction isolation

levels that can be used to solve similar problems. The developer might choose an

isolation level where concurrent access is allowed only to read operations, while only one

user may write data at a given time. A setting like this will definitelly increase

performance as opposed to letting one user at a time regardless if they want to read or

write data.

It is also important how complex the database will be. Are there going to be complex

relationships between the tables, primary keys represented by multiple fields, as well as

how often are they going to change.

1.2 Problem Statement

There are plenty of data access technologies to choose from in J2EE server

applications. Some are more popular than others are. They all have their pros and cons.

Some are simpler to use, while others get better performance.

8

It is not always easy to choose one for an application. The right choice of data access

technology depends on how the application needs to access the data. These technologies

differ in performance and the way of data access and representation. They also differ in

the level of transaction support and isolation control. Some offer more and others less.

This in particular, is the goal of this thesis. It compares a couple of commonly used

APIs that interact with relational databases - JDBC, Stored Procedures, Enterprise Java

Beans and the Spring framework JDBC API, how they can be applied to solve a

particular problem and how to choose the right approach. However, the focus is on

performance standpoint. The most common problem in real world is the speed of

database operations. Data access operations typically happen in bursts.

1.3 Solution Strategy

Running real tests is the best way to see how fast each of these technologies is. In

order to do that, we need a simple server application to run tests against data. This simple

application will allow the user to choose a technology and the amount of data to read.

The application also needs to allow the user to select various amounts of data to use.

Using the selected technology and amount of data, the application will need to do some

processing, display the results and how much time it took to do the job. With this

application, it will be possible to do the necessary tests in order to draw conclusions. A

graph representing these findings is given for better readability and understanding.

9

1.4 Thesis roadmap

The central focus of this thesis is to establish which one, from a set of database access

technologies, performs best. For that purpose a test application that uses these database

access technologies will be developed, and then used to run tests against real data. Based

on the results, conclusions will be drawn and some rules on how to choose the right

direction when using a database will be given. Accordingly, the remainder of this thesis is

organized as follows:

Chapter 2: J2EE Data Access Technologies

Chapter 2 provides a brief introduction on each of the database access technologies-

JDBC, Stored Procedures, EJB and Spring JDBC. It also discusses how they differ

among each other.

Chapter 3: Design and Implementation of Benchmark Application

This chapter provides a detailed description of the application used to run tests.

Chapter 4: Experimental Study

Describes the testing process and summarizing data

Chapter 5: Conclusion

The final chapter is where conclusions are made based on the experiment results

10

Chapter 2

J2EE Data Access Technologies

Many real-world Java 2 Platform, Enterprise Edition need to use persistent data at

some point. In many applications, persistent storage is implemented using different

mechanisms, and there are different APIs used to access these mechanisms. For example,

the data may reside in Lightweight Directory Access Protocol (LDAP) repositories,

mainframe systems, and so forth. Another example is where data is provided by services

through external systems such as business-to-business (B2B) integration systems, credit

card bureau service, and so forth. However, the most commonly used storage mechanism

is a relational database.

Typically, applications use distributed components such as entity beans to represent

persistent data. An application is considered to employ bean-managed persistence (BMP)

for its entity beans when these beans explicitly access the persistent storage-the entity

bean includes code to directly access the persistent storage. An application with simpler

requirements may instead use session beans or servlets to directly access the persistent

storage to retrieve and modify the data. Or, the application could use entity beans with

container-managed persistence (CMP), and thus let the container handle the transaction

and persistent details.

Applications can use the JDBC API to access data residing in a relational database

management system (RDBMS). The JDBC API enables standard access and

manipulation of data in persistent storage, such as a relational database. The JDBC API

enables J2EE applications to use SQL statements, which are the standard means for

11

accessing RDBMS tables. However, even within an RDBMS environment, the actual

syntax and format of the SQL statements may vary depending on the particular database

product.

There is even greater variation with different types of persistent storage. Access

mechanisms, supported APIs, and features vary between different types of persistent

stores such as RDBMS, object-oriented databases, flat files, and so forth. Applications

that need to access data from a legacy or disparate system (such as a mainframe, or B2B

service) are often required to use APIs that may be proprietary. Such disparate data

sources offer challenges to the application and can potentially create a direct dependency

between application code and data access code. When business components-entity beans,

session beans, and even presentation components like servlets and helper objects for

JavaServer Pages (JSP) pages --need to access a data source, they can use the appropriate

API to achieve connectivity and manipulate the data source. But including the

connectivity and data access code within these components introduces a tight coupling

between the components and the data source implementation. Such code dependencies in

components make it difficult and tedious to migrate the application from one type of data

source to another. When the data source changes, the components need to be changed to

handle the new type of data source.

These problems are adressed using a Data Access Object (DAO) to abstract and

encapsulate all access to the data source. The DAO manages the connection with the data

source to obtain and store data.

12

At this time J2EE offers a couple of data access technologies. They have been around

for a while, and they are part of the J2EE specification:

1. JDBC (Java Database Conectivity)

2. Stored Procedures (JDBC is used here too except that all the database processing

is done in the database)

3. EJB (Entity Java Beans)

Some developers felt that EJB was too heavy for some situations. That is why, not that

long ago, the open source community came up with a new concept, although not part of

J2EE, but increasingly used by major Dot Com companies, known as:

4. Spring Framework JDBC –one of the APIs from the Spring Framework.

The Spring reference document claims that Spring JDBC is be lighter than the EJB

container and offers the same features of EJB at the same time.

The following is supposed to introduce the reader to each of these technologies

separately.

2.1 JDBC based data access

The first one, JDBC, is the first data access technology for Java based applications.

Every other technology is sort of an upgrade of JDBC. It offers a simple way to execute

SQL statements against a relational database and retrieve the results. It also offers

transaction control so that two or more statements can be executed atomically.

Transaction isolation is provided too, so it is possible to specify that another transaction

can not change the data while the current transaction is in progress. This will prevent

13

from having two transactions affecting the same data simultaneously. With four levels of

transaction control, JDBC has enough to provide concurrent read control. Using JDBC

business processing is done outside of the database so the developer has full control over

the processing. However, this may not always be desired.

Since the code is database specific, code changes will be needed each time the

application needs to migrate to another database. Code changes will also be needed if

some business rule changes.

And finally there is one more problem that JDBC transactions do not address. There

is no easy way to join resources that are not part of JDBC together with JDBC operations

within a single transaction. For example, a common problem in many applications is the

following: An application takes orders for widgets over the Internet. The widget order-

processing application is a legacy system that takes orders from a queue and processes

them in a batch so that they are eventually delivered to the customers. This means that the

order might have to wait some time in the queue before it gets processed.

This model doesn’t satisfy the expectations of an Internet order-entry application.

Users expect to be able to check the order of the application at any time. However, that

might not always be possible in a system where the order may sit in a queue for a long

time while in between processing steps. It would also be too cumbersome to have to

check several queues to determine the progress of the order.

Instead, what is often done is to have two different representations of the order –

when the order is first received, a record is created in a relational database to represent

the order, and the order is then placed in the first state of the queue for the order-

14

processing system to start processing it. As the order moves from one state to another, the

information in the database is updated accordingly.

So far, so good but the problem that occurs is when the database record needs to be

created and the order sent to a queue in the same transaction. It’s not acceptable if the

database record creation fails and the order processing enqueuing succeeds. That would

result in a user being billed and having widgets delivered but not being able to find out

anything about the order in the meanwhile. The two resources- the JDBC connection and

the JMS queue need to be joined in a single transaction, but JDBC transaction control

can’t do that. There is something called 2-Phase Commit (2-PC) transaction model and it

can be used to resolve the problem. 2-PC will be mentioned again when discussing EJB

(Entity Java Beans), although not in detail because that is out of the scope of this

discussion.

The conclusion is, JDBC is simple and easy to use, but there are situations where it is

not enough. It doesn’t have the flexibility some applications might require.

2.2 Stored procedures

A Stored procedure uses JDBC behind the scenes to do its job. It also requires that the

database vendor provides support for stored procedures, as is the case with most

databases today. With this technology all the business processing can be done in the

database itself. This removes unecessary network communication thus improving

performance dramatically. It also improves code reuse, because the logic is coded only

once and stored in the database where application code can call it. The benefit of this is

that the developer doesn’t have to change code (possibly at multiple locations in the

15

program) each time a business rule changes. Only the stored procedure itself has to be

changed.

Stored Procedures offer great flexibility. In case the application needs to switch from

one database vendor to another one, for example from MS SQL database to a MySQL

database, all that needs to be done is move the stored procedures to the MySQL database,

and change the appropriate driver that JDBC uses to talk to the database. Due to slight

differences in Stored Procedures semantics from one vendor to another, it may be needed

to modify the procedures accordingly, but the point is that no changes would be needed in

the client code.

When using a stored procedure, the client doesn’t know what kind of processing is

done behind the scenes. That is irrelevant. The only drawback of this scenario is that the

developer doesn’t have full control over the application. If the database is running on a

dedicated database server, the developer would need to consult a database administrator

for every change to the stored procedure or even worse, the developer might have

restricted access to the server.

Until recently the only way to write a stored procedure in a database was by using

special set of keywords similar to SQL, so everyone who wanted to write stored

procedures had to learn this language. Nowadays, more and more database vendors offer

writing Java based stored procedures. That is, all the processing can be written in a Java

class using JDBC. This class is then compiled and stored in the database for faster

processing.

16

Store Procedures are the most often data access technology used in the industry today.

They offer everything that JDBC does, plus speed, making them the choice of many

companies.

2.3 EJB (Entity Java Beans)

As a relatively new technology, EJB was developed to make it easier to write object

oriented code that interacts with relational databases. EJB specifies three kinds of beans:

session beans, entity beans and message driven. Entity beans are the ones that are used to

communicate to a relational database. Therefore this thesis will focus on entity beans

only.

Entity beans give an object-oriented representation of traditional relational databases.

The entity bean updates, inserts or deletes rows from a database table. Most often, each

table in a database maps to an entity bean and the rows of the table are different instances

of the bean. This implies that entity beans are persistent; their information is persisted in

the database after the server shuts down, similar to a database that does not lose its data

when the machine shuts down.

There are two kinds of entity beans – those with container managed persistence

(CMP) and those with bean managed persistence (BMP).

Entity beans, as all enterprise beans, must run in an EJB container, and only the

container can invoke their methods directly. Clients invoke methods through a proxy, and

then the container invokes the appropriate methods on the bean itself. An entity bean has

a sofisticated lifetime controlled by the container. Figure 2 Life Cycle of an Entity Bean

shows the stages that an entity bean passes through its lifetime. After the EJB container

17

creates the instance, it calls the setEntityContext method of the entity bean class. This

method passes the entity context to the bean.

After instantiation, the entity bean moves to a pool of available instances. While it is

in the pooled stage, this instance is not associated with any particular EJB object identity.

In the pool all instances are identical. The EJB container assigns an identity to an instance

only when moving it to the ready stage.

Figure 2 Life Cycle of an Entity Bean

There are two paths from the pooled stage to the ready stage. On the first path, the

client invokes the create method, causing the EJB container to call the ejbCreate and

ejbPostCreate methods. On the second path, the EJB container invokes the ejbActivate

method. While an entity bean is in the ready stage, it's business methods can be invoked.

18

There are also two paths from the ready stage to the pooled stage. First, a client can

invoke the remove method, which causes the EJB container to call the ejbRemove

method. Second, the EJB container can invoke the ejbPassivate method.

At the end of the life cycle, the EJB container removes the instance from the pool and

invokes the unsetEntityContext method.

While in the pooled state, an instance is not associated with any particular EJB object

identity. With bean-managed persistence, when the EJB container moves an instance

from the pooled state to the ready state, it does not automatically set the primary key.

Therefore, the ejbCreate and ejbActivate methods must assign a value to the primary key.

If the primary key is incorrect, the ejbLoad and ejbStore methods cannot synchronize the

instance variables with the database.

In the pooled state, the values of the instance variables are not needed. The user can

make these instance variables eligible for garbage collection by setting them to null in the

ejbPassivate method.

Both CMP and BMP entity beans use JDBC behind the scenes. In the case of

container managed persistence entity beans the developer doesn’t have to write JDBC

code directly. The developer codes the business logic and defines how the bean maps to

the database by defining abstract getter and setter methods for each field. Then, a

deployment tool is used to generate the tedious JDBC code which does all the work

behind the scenes. As mentioned above, EJBs live in an EJB container, and the

deployment tool generates container specific code.

19

The EJB container is part of a J2EE application server such as WebSphere, WebLogic

or JBoss, and is described in the J2EE specification. The developer can focus on writing

the business logic, defines the fields and relationships without being burdened by writing

cumbersome JDBC code. Optional finders and select methods may be specified using a

special EJB-QL syntax language. Then, this code is run through a deployment tool to

generate the complete bean including JDBC code which operates behind the scenes.

In the sample test application, IBM’s WebSphere Application Server will be used

along with Rational Application Developer as the development tool (IDE). This tool

includes the deployer needed to generate WebSphere specific bean code. If JBoss was

used instead, a little more effort would be needed to learn how to use the open source

deployment tool known as XDoclet.

The deployer tool can even create script files that create the database infrastructure

needed to support the entity beans and any relationships between them. This makes it

very simply to migrate the code to a different application server or database by just re-

running the deployment tool.

JDBC couldn’t handle the order-processing scenario discussed in JDBC based data

access, because it doesn’t have the power to define a transaction that includes a non-

JDBC operation. EJB overcomes this problem with container-managed transactions

which in turn support the 2-PC (2 Phase Commit) protocol. The only requirement is that

the JDBC driver used in a 2-PC protocol behind the scenes has to be XA compliant. Most

database vendors provide this driver nowadays. The EJB container also supports

20

transactions that span multiple remote servers, a feature that no other technology offers

till date.

Entity beans can also help a lot if complex relationships need to be maintained. There

are tools available that make this otherwise cumbersome process as easy as drawing a

graph.

Entity beans support a similar to SQL language known as EJB-QL. This language is

ment to be used in conjunction with special methods known as finder and select methods.

These methods allow selecting a particular bean instance, a set of beans or just a field.

EJB-QL queries can navigate between relationships where this is possible.

Alternatively to CMP (Container Managed Persistence) entity beans, there are BMP

(Bean Managed Persistence) entity beans where the developer doesn’t use a deployment

tool but rather writes the JDBC code manually. The advantage of using BMP entity beans

is that developers may optimize the SQL statements as they need, while having to write

the code themselves. This might be necessary in some special situations.

2.4 Spring Framework

The Spring Framework was created by a group of enthusiasts who thought existing

J2EE technologies can get better. Today Spring is being used by a large number of

companies, including major Dot Coms.

Spring contains a lot of features, which are well-organized in six modules shown in

Figure 3 Overview of the Spring Framework below.

21

Figure 3 Overview of the Spring Framework

Spring doesn’t impose itself wholly on to the design of a project. A developer can

choose to use only certain part from Spring and the rest of the application can be

implemented using any other API. No matter what other framework is used, Spring will

coexist without causing any problems. It is a comprehensive framework including an

MVC framework, a JDBC abstraction layer, EJB support, AOP integration framework, as

well as support for major O/R mapping tools such as Hibernate and JDO.

The DAO package provides a JDBC-abstraction layer that eliminates the need to

write boilerplate JDBC code and deal with database-vendor specific error codes. The,

DAO package provides a way to do programmatic as well as declarative transaction

22

management, not only for classes implementing special interfaces, but for all POJOs

(Plain Old Java Objects). Declarative transaction management is accomplished using the

AOP support built in Spring, and this is the preffered way to use a transaction since it

doesn’t couple code to any API.

The JDBC abstraction layer is included within the DAO package. This thesis will

only focus on this part of the Spring Framework, referring to it as Spring JDBC.

Spring JDBC uses JDBC behind the scenes, except that the developer doesn’t have to

write any of the boilerplate code used for handling resources with a typical JDBC

application. Perhaps the best way to show the value-add provided by Spring JDBC is by

the following list:

1. Define connection parameters

2. Open the connection

3. Specify the statement

4. Prepare and execute the statement

5. Set up the loop to iterate through the results (if any)

6. Do the work for each iteration

7. Process any exception

8. Handle transactions

9. Close the connection

23

The list shows the steps in code needed when using standard JDBC code. The bold

italicized lines show the steps needed when using Spring JDBC. It is clear that there is

substantially less code when using Spring JDBC.

All exceptions thrown during JDBC processing are translated to exceptions defined in

the org.springframework.dao package. An application that uses Spring JDBC does not

need to deal with database specific error code handling. All translated exceptions are

unchecked runtime exceptions allowing the developer to catch only recoverable

exceptions and let other exceptions to propagate to the caller.

24

Chapter 3

Benchmark Application

3.1 Design

A benchmark application will be used to run tests with different data access

technologies. This simple application will allow the user to switch between diferent data

access technologies. It will also allow the user to choose the amount of data used in the

test. In the end it would display the time it took to do the job.

In this case the application works with stock data retrieved from a database.

Obviously, the database will need to be populated with some data. Here are the guidelines

of the application:

-The user can choose whether all stocks prices to be displayed or just the cheapest

ones. This is supposed to show the difference between simply returning data from the

database and returning data after some processing is done against it.

-The user can select which data access technology (JDBC, Stored Procedures, EJB or

Spring JDBC) will be used in the test.

-The database used for testing will be populated with 20000 rows of stock records.

Obviously, we will need a piece of code that will generate and store this data in the

25

database. Since this is a benchmark application the user needs to be able to select the

number of rows included in the search.

-After each operation completes the user needs to know how much time it took.

Figure 4 Benchmark application flow shows the application flow diagram:

JSP page for user input &

results

JDBC

EJB

Stored Procedures

Spring JDBC

All stocks

Cheapest stocks

All stocks

Cheapest stocks

All stocks

Cheapest stocks

All stocks

Cheapest stocks

DB

Amount of data to use

Figure 4 Benchmark application flow

The user opens his browser to the application URL and a page is displayed. This page

has combo boxes from which he can select the amount of data to be used, data access

technology and whether all or the chapest stocks should be displayed.

Then the user clicks a button and receives the results back in a table. Along with the

results, the page returns how much time the operation took.

26

3.2 Implementation

The benchmark application is implemented using the MVC (Model View Controller)

pattern. MVC is a proven pattern and is very popular nowadays. In a J2EE server

application like this one, this means that JSPs (Java Server Pages) are used for the

presentation logic. A servlet controls the execution of business rules and presentation

logic. Data itself is stored in the Model – usually JavaBean components and/or a

database.

In this application, a servlet controls what technology is used to retrieve stock data.

This servlet simply processes user selections and invokes the corresponding method.

Each of these methods will return a Vector object. This object contains the stock objects.

The servlet then passes this Vector to the JSP page to display the results by iterating the

vector.

A custom JavaBean class named StockDetails will represent each stock object in the

vector. This bean will contain all the details to describe a particular stock. In this case, it

contains the stock index, full name of the company, value, its up/down values for the day,

the value it had at the end of the previous business day, the lowest and highest value for

the day and time of retrieving the data. Being a JavaBean implies that the class will have

a getter and setter method for each of these properties and a no argument constructor.

Before running any tests, the database has to be populated with 20000 records of

stock information. A simple loop generates random stock records and writes them to the

database so that they can be used for testing later.

27

In terms of deployment, the whole application is packaged in an Enterprise Archive

(EAR) file. This is mostly because the application uses Enterprise Java Beans. If there

were no EJBs used, the application could have been deployed as a Web Archive (WAR)

file. This is because EJBs cannot be deployed as a web archive. They have their own,

separate (Java Archive) JAR module. This module contains an ejb-jar.xml descriptor file

that describes each enterprise bean, similarly as the web.xml descriptor file describes

components in a web archive.

An EAR file can include multiple WAR and JAR files. This EAR will have one JAR

file that contains the enterprise beans, a WAR file for the rest of the application and

probably some libraries that need to be used by both the web archive and enterprise beans

archive. When the enterprise archive is deployed on a J2EE application server like

WebSphere, the server extracts the archives encapsulated within and uses the descriptor

files to configure any resources used by them.

The web archive in this case contains everything except for the enterprise beans. This

includes the JSP page, a servlet and a few utility classes. What the user sees is the JSP

page. This is the page for user interaction. The same page also displays the results. The

servlet does all processing behind the scenes by processing user input and invoking

methods on the utility classes. All J2EE web applications must follow the structure

shown in Figure 5 Web Application Structure in order to be exported as a web archive

file, and deployed on a J2EE application server.

28

Figure 5 Web Application Structure

The web.xml descriptor file is present in most J2EE server applications. It can contain

important information about servlet mappings, resources, welcome pages etc… In the

benchmark application, the web.xml descriptor file contains a servlet definition and

mapping for the controller servlet. Since the application uses the Spring MVC in part, this

file also has a definition and mapping for the dispatcher servlet that comes with Spring.

The application also needs access to the enterprise beans, which are in a separate

archive. For the servlet to be able to lookup an enterprise bean, it needs a reference to its

JNDI name.

The servlet uses a data source directly when using JDBC or Stored Procedure call.

The best way to do this is to have the data source bound in the JNDI namespace, and then

provide the servlet with a reference to that name. A data source is nothing but a pool of

connections maintained on the server for better performance. Because creating

connections is an expensive operation, maintaining a pool of connections is faster and it

29

is the preferred way of connecting to a database from a J2EE application. A full listing of

the web.xml file is provided at the end of this thesis.

The controller servlet in the application is named Dispatcher. It is a typical

HttpServlet subclass used to process GET and POST requests. To get to the application, a

user browses to a URL similar to this one http://someserver.com:9080/Stock/stockList.

The port number 9080 is the default port where WebSphere listens for incoming requests.

Analyzing web.xml it is obvious that requests going to /stockList map to the Dispatcher

servlet. Its job is to process every request, validate user input, and depending on what the

user selected, get the actual stock records. Then it forwards these results along with the

request to the page, which displays them to the user.

switch(Integer.parseInt(selectedOption))

{case ALL_PRICES: stocks=TestData.getDataJDBC(rows);break;

case CHEAPEST_PRICES: stocks=TestData.getCheapestDataJDBC(rows);break;

case ALL_PRICES_SP: stocks=TestData.getDataSP(rows);break;

case CHEAPEST_PRICES_SP: stocks=TestData.getCheapestDataSP(rows);break;

case ALL_PRICES_EJB: TestData testData=new TestData(); stocks=testData.getDataEJB(rows);break;

case CHEAPEST_PRICES_EJB: testData=new TestData(); stocks=testData.getCheapestDataEJB (rows);break;

case ALL_PRICES_SPRING:

case CHEAPEST_PRICES_SPRING:

springpage=true;

try { request.getRequestDispatcher("/stockList.htm").forward(request, response); }

catch (Exception ex) { System.out.println("i crashed here "+ex.getMessage());};break;

The interesting bit here is how the Spring JDBC case is handled. Because Spring has

its own mechanisms for processing requests, in case the user chooses one of the cases

where Spring JDBC is used, the servlet simply forwards the request to “/stockList.htm”.

30

This is not a real page, but looking back at the web.xml file every URL that ends with

‘.htm’ extension is mapped to the Spring servlet. The dispatcher servlet lets Spring

handle user input, get the results and display them.

Besides processing data this servlet also calculates how much time the operation took.

This value is obtained in a very simple way:

long start=System.currentTimeMillis();

/* do some processing here */

long end=System.currentTimeMillis();

...

request.setAttribute("duration",new Long((end-start)).toString());

This last line of code passes the result to the request to be forwarded to the page.

A full listing of the dispatcher servlet is provided at the end of this document. There

are eight different ways (two for each one of the four different data access technologies)

to get the stocks depending on what the user chooses. A convenient switch statement

handles these eight cases. The last two of them, where Spring technology is used, are not

handled by the Dispatcher servlet. They are propagated to the Spring servlet instead. The

other cases are handled by the servlet by invoking various methods on a utility class

named TestData. One thing that these methods have in common is that they all return a

vector of StockDetails objects. StockDetails is just another utility class that holds details

for a particular stock record.

When a user chooses to get all data using JDBC, the servlet invokes the method

getDataJDBC(String howmany). The parameter howmany, specified by the user,

determines the number of database records to be included in the operation. The method

31

simply executes a SQL query and puts the results in a vector of StockDetails objects. The

following is an excerpt from this method:

preparedStatement = getConnection().prepareStatement("SELECT * FROM DB2ADMIN.STOCKS FETCH FIRST

"+rows+" ROWS ONLY");

resultSet = preparedStatement.executeQuery();

StockDetails stockDetails = null;

while (resultSet.next())

{stockDetails = new StockDetails(…);

stocksVector.add(stockDetails);}

A PreparedStatement object compiles and stores the SQL statement in database. This

way the same statement can be executed multiple times efficiently. In the benchmark

application, this is not very useful since all data is grabbed at once, but it doesn’t degrade

performance in any way, so it is good practice to use it. The SQL statement from this

method uses a special set of keywords - “FETCH FIRST N ROWS ONLY”. This means

that only the first N rows are used. Then the statement executes, and the code iterates

through the returned ResultSet object. For every record returned, a new StockDetails

object is created and appended to the resulting vector object.

If the user chose to get the cheapest data using JDBC the method

getCheapestDataJDBC(String howmany) is invoked. This method returns a vector of

StockDetails object too. The same SQL statement is used again to grab the records from

database. The different bit is during iteration of the results - only the cheapest stocks are

added to the vector object:

while (resultSet.next()) {

index = resultSet.getString(1);

boolean exists = false;

32

int i;

for (i = 0; i < stocksVector.size(); i++) {

StockDetails existingStockDetails = (StockDetails) stocksVector.elementAt(i);

if (existingStockDetails.getIndex().equals(index))

{exists = true;

if (existingStockDetails.getValue() > resultSet.getDouble(3))

{stocksVector.remove(existingStockDetails);

stocksVector.add(new StockDetails(…));

}

break; }

}

if (!exists) {stockDetails = new StockDetails(…);

stocksVector.add(stockDetails);}

}

The code above iterates through a ResultSet object and for each stock record it checks if it

already exists in the resulting vector object. If it does not exist, it adds it to the vector,

otherwise checks the stock price and adds it only if it is cheaper. That way the vector will

contain only the cheapest stocks.

If the user chooses to get all data using a stored procedure then the method

getDataSP(String howmany) is called. In this case, instead of executing a SQL statement,

the method calls a stored procedure. A CallableStatement object is used to call the stored

procedure getStocksLimited(int rows). The stored procedure returns a ResultSet object of

stock records from the database. Then the method simply iterates through this object

adding a StockDetails object to the vector per iteration.

callableStatement = getConnection().prepareCall("{call DB2ADMIN.getStocksLimited(?)}");

callableStatement.setInt(1,rows);

resultSet = callableStatement.executeQuery();

33

StockDetails stockDetails = null;

while (resultSet.next())

{stockDetails = new StockDetails(…);

stocksVector.add(stockDetails);}

The stored procedure getStocksLimited(int rows) is implemented as a Java class and

stored in a DB2 database. DB2 comes with its own JDK 1.2 in order to support this

feature. DB2 also supports another option – stored procedures implemented with a

specific set of SQL keywords, an option supported by most database vendors today. The

parameter rows, determines how many records will be retrieved from the database. A full

listing of the GETSTOCKSLIMITED.java class is provided at the end of this document.

If the user wants to get only the cheapest stocks from a set of n stock records using a

stored procedure, the method getCheapestDataSP(String howmany) is called. This

method uses a CallableStatement object to call another stored procedure named

getCheapestStocksLimited(int n). The parameter n means that the first n records will be

looked to get the cheapest stocks. In this case, the search for minimum prices happens in

the procedure itself. The procedure is implemented as a Java class named

GETCHEAPESTSTOCKSLIMITED.java, which is compiled and stored in the DB2

database. A listing of this class is provided at the end of this document. The following

SQL statement executes in the procedure:

SELECT DB2ADMIN.STOCK.INDEX1, DB2ADMIN.STOCK.FULLNAME, DB2ADMIN.STOCK.VALUE1,

DB2ADMIN.STOCK.UP1, DB2ADMIN.STOCK.DOWN1, DB2ADMIN.STOCK.PREVIOUSCLOSE,

DB2ADMIN.STOCK.DAYSLOWVALUE, DB2ADMIN.STOCK.DAYSHIGHVALUE,

DB2ADMIN.STOCK.TIMEOFRETRIEVAL FROM DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.VALUE1

IN (SELECT MIN(DB2ADMIN.STOCK.VALUE1) FROM DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.ID

< rows GROUP BY DB2ADMIN.STOCK.INDEX1 ) AND DB2ADMIN.STOCK.ID < rows;

34

The above statement returns the cheapest stocks from certain number of stock records

using the MIN keyword available in SQL.

In case the user wants to get all stocks using entity beans the method

getDataEJB(String n) is used to get the results. This methods uses enterprise beans to get

the first n stocks from database. When using EJB the client typically does not invoke the

entity bean directly. Usually the client invokes a session bean, which delegates the call to

the entity bean. This is to provide better security and scalability. So, the method simply

locates a session bean named StockManager and calls the method getCheapestStocks() on

this bean.

public Collection getDataEJB(String howMany) {

int rows;

try {rows=Integer.parseInt(howMany);}

catch (NumberFormatException e1) {return null;}

StockManagerLocal aStockManagerLocal = createStockManagerLocal();

return aStockManagerLocal.getAllStocksLimited(new Integer(rows));

}

The method createStockManagerLocal() locates the session beans home interface by its

JNDI name and invokes create() to get a thread safe instance of the session bean. Then it

calls the method getAllStocksLimited(Integer n) from the StockManager session bean:

public Collection getAllStocksLimited(Integer rows) {

aStockLocalHome = getStockLocalHome();

Vector result = new Vector(2000);

Collection stocks = null;

try {stocks = aStockLocalHome.findAllLimited(rows);}

catch (javax.ejb.FinderException fe) {return new Vector();}

Iterator iterator = stocks.iterator();

35

while (iterator.hasNext()) {

StockLocal stockLocal = (StockLocal) iterator.next();

StockDetails stock = new StockDetails(…);

result.add(stock);}

return result;}

The first thing this method does is locating the home interface of the Stock entity bean.

As with the session bean, again the JNDI name is used to get the home interface. Then a

special finder method named findAllLimited(Integer rows) is invoked on this interface. A

finder method is special because it does not have to be implemented when using a CMP

(Container Managed Persistence) entity bean. However, a query has to be defined in the

ejb-jar.xml descriptor. A special language known as EJB-QL (Enterprise Java Bean –

Query Language) is used to specify finder and/or select methods. The query definition is

embodied in a <query/> tag. This one takes a parameter to specify how many records will

be returned:

<query>

<description></description>

<query-method>

<method-name>findAllLimited</method-name>

<method-params>

<method-param>java.lang.Integer</method-param>

</method-params>

</query-method>

<ejb-ql>select object(o) from Stock o where o.id &lt; ?1</ejb-ql>

</query>

The finder method returns a collection of Stock bean instances. Because the servlet

expects a vector of StockDetails objects, the method getAllStocksLimited from the

StockManager bean loops through the collection and populates the resulting vector.

36

In case the user wanted to get the cheapest stocks using EJB the method getCheapest-

DataEJB(String howmany) is called in the dispatcher servlet. Once again as when getting

all stocks using EJB, this method simply obtains a StockManager session instance and

calls a method, this time named getCheapestStocksLimited(String howmany).

public Collection getCheapestDataEJB(String howMany) {

int rows;

try {rows=Integer.parseInt(howMany);}

catch (NumberFormatException e1) {return null;}

StockManagerLocal aStockManagerLocal = createStockManagerLocal();

return aStockManagerLocal.getCheapestStocksLimited(new Integer(rows));}

Once again the method createStockManagerLocal() is used to get instance of the session

bean using its JNDI name behind the scenes. The different bit is that another method –

getCheapestStocksLimited(Integer rows) is invoked on the session bean this time. This

method first locates a home instance of the entity bean and then invokes the

findAllLimited(Integer rows) finder method from this instance. The same finder method

was invoked in the previous case when all stocks were retrieved.

One anomaly of EJB-QL is that it doesn’t provide a keyword for getting the minimum

value, something like the MIN() function in SQL. This is why the session bean method

iterates through the results from the finder method and puts the ones with lowest prices in

a vector of StockDetails objects:

public Collection getCheapestStocksLimited(Integer rows) {

aStockLocalHome = getStockLocalHome();

Vector result = new Vector(2000);

Collection stocks = null;

try {stocks = aStockLocalHome.findAllLimited(rows);} catch (javax.ejb.FinderException fe) {return new Vector();}

37

Iterator iterator = stocks.iterator();

String index = null;

while (iterator.hasNext())

{StockLocal stockLocal = (StockLocal) iterator.next();

index = stockLocal.getIndex();

double value = stockLocal.getValue().doubleValue();

boolean exists = false; int i;

for (i = 0; i < result.size(); i++)

{StockDetails existingStockDetails = (StockDetails) result.elementAt(i);

if (existingStockDetails.getIndex().equals(index))

{exists = true;

if (existingStockDetails.getValue() > value)

{result.remove(existingStockDetails);

result.add(new StockDetails(…));}

break;}

}

if (!exists)

{result.add(new StockDetails(…));}

}

return result;

}

The method initially gets all stock records using the finder method and then iterates

through the collection of StockLocal objects. For each of them it checks whether it

already exists in the vector. If it does not already exist it adds it, otherwise it replaces it

only if the existing one has a bigger price. This way the vector contains unique instances

of the cheapest stocks. The above code is nothing but a workaround to the fact that EJB-

QL does not provide a built in function to get the minimum values. That takes processing

out from the database into client code.

38

If the user chooses to use Spring JDBC to get stock data, regardless if it is all stocks

or only the cheapest ones, the dispatcher servlet forwards the request to the Spring servlet

to deal with it. This servlet is distributed with a Spring JAR file, and it is defined in the

web.xml descriptor file of the web application – deployed as a WAR file. A mapping

URL is also defined for the Spring servlet in web.xml:

<servlet>

<servlet-name>stocks</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

...

<servlet-mapping>

<servlet-name>stocks</servlet-name>

<url-pattern>*.htm</url-pattern>

</servlet-mapping>

This definition gives the name “stocks” to the Spring servlet and maps all request

ending with .htm to this servlet. That is exactly what the dispatcher servlet does – it

forwards these requests to “/stockList.htm”. During initialization, the “stocks” servlet

looks for a special configuration file, named [servlet-name]-servlet.xml, or in this case

stocks-servlet.xml. This file defines how the servlet handles requests for

“/stockList.htm”:

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/stockList.htm">stockListController</prop>

</props>

</property>

39

</bean>

The configuration specifies that any incoming requests to this url be handled by

stockListController. The definition for this controller, and a couple of other definitions

referenced by the controller are given in the same file.

<bean id="stockListController" class="com.dimitri.controllers.StockListController">

<property name="stockManager">

<ref bean="stockManager"/>

</property>

</bean>

<bean id="stockManager" class="com.dimitri.spring.db.StockManager">

<property name="stockManagerDao">

<ref bean="stockManagerDao"/>

</property>

</bean>

<bean id="stockManagerDao" class="com.dimitri.spring.db.StockManagerDaoJdbc">

<property name="dataSource">

<ref bean="dataSource"/>

</property>

</bean>

...

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName" value="java:comp/env/mybank"/>

</bean>

The concept of Inversion of Control (IoC) makes Spring different than other

frameworks. This is where it comes in action. Spring provides the Controller interface,

whose purpose is to simplify request handling. This interface specifies a method named

handleRequest(…). The StockListController class implements this interface, overriding

the handleRequest() method to get user parameters and get the data.

40

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String rows=request.getParameter("rows");

String selectedOption=request.getParameter("selectedScreen");

if(selectedOption==null)selectedOption="0";

if(rows==null)rows="1000";

int n=Integer.parseInt(rows);

Map model=new HashMap();

long start=System.currentTimeMillis();

switch(Integer.parseInt(selectedOption))

{case Dispatcher.ALL_PRICES_SPRING:model.put("stocks", stockManager.getStocks(n));break;

case Dispatcher.CHEAPEST_PRICES_SPRING:model.put("stocks", stockManager.getCheapestStocks(n));break;}

long end=System.currentTimeMillis();

model.put("duration",new Long((end-start)).toString());

return new ModelAndView("stockListSpring", "model", model);

}

The method handles two cases, all stocks or cheapest stocks using Spring JDBC,

depending what the user chooses, because those two are the only ones that ever get here

from the dispatcher servlet. The method also measures how long the operation took. It

puts all this data into a simple model (Map object) and forwards it to the view to display

them.

StockListController also has getter and setter methods for the StockManager

property. This property is set by the IoC container with a singleton instance of

StockManager. This class has a setter and getter method for the StockManagerDao

property. StockManagerDao is an interface, and an implementation of this interface is the

StockManagerDaoJdbc class. The IoC container sets this property to a singleton instance

of the implementation class. This class uses Spring JDBC to get stock records from the

database.

41

The implementation class StockManagerDaoJdbc needs a data source to connect to

the database and it has a setter and getter method for the datasource property. Once

again, the IoC container sets this property with a singleton data source instance

maintained in the configuration file. The class JndiObjectFactoryBean is part of Spring

and is useful for locating JNDI bound objects like the data source in this case. It is very

common to bind a data source to a JNDI name when using application servers.

StockManagerDaoJdbc provides implementation of the getStockList(int n) method which

is called when a user wants to get all stock records using Spring JDBC.

public List getStockList(int n) {

StockQuery stockQuery=new StockQuery(ds,n);

return stockQuery.execute();

}

The method takes a parameter that determines how many records from database will be

used in the operation.

The ds object is nothing else but a data source instance set from the IoC container

using a setter method:

public void setDataSource(DataSource ds)

{this.ds = ds;}

A nested class called StockQuery is the place where Spring JDBC API is used:

class StockQuery extends MappingSqlQuery {

StockQuery(DataSource ds, int rows) {

super(ds, "SELECT INDEX,FULLNAME,VALUE,UP,DOWN,PREVIOUSCLOSE,DAYSLOWVALUE,"

+ " DAYSHIGHVALUE,TIMEOFRETRIEVAL FROM DB2ADMIN.STOCKS FETCH FIRST "+rows+" ROWS

ONLY");

compile();}

42

protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {

StockDetails stockDetails = new StockDetails();

stockDetails.setIndex(rs.getString("INDEX"));

stockDetails.setFullName(rs.getString("FULLNAME"));

stockDetails.setValue(rs.getDouble("VALUE"));

stockDetails.setUp(rs.getDouble("UP"));

stockDetails.setDown(rs.getDouble("DOWN"));

stockDetails.setPreviousClose(rs.getDouble("PREVIOUSCLOSE"));

stockDetails.setDaysLowValue(rs.getDouble("DAYSLOWVALUE"));

stockDetails.setDaysHighValue(rs.getDouble("DAYSHIGHVALUE"));

stockDetails.setTimeOfRetrieval(rs.getDate("TIMEOFRETRIEVAL"));

return stockDetails;

}

}

This class is a subclass of MappingSqlQuery, which is part of Spring JDBC. The SQL

statement is passed in the constructor, and then the method compile() is called. This

method makes sure the statement is valid and all parameters are in the proper format. The

statement isn’t executed until the method execute() is called. This method was called in

getStockList(int n).

Another inherited method is mapRow(ResultSet rs, int rowNum). Spring calls this

method for every row in the ResultSet object returned when executing the statement. In

the benchmark application, it creates a StockDetails object for every row. The method

execute() forms a list of these objects and that is what gets returned to the caller – a List

of StockDetails objects.

Otherwise if the choice was to get the cheapest stocks using Spring JDBC, the

method getCheapestStocks(int n) from StockManagerDaoJdbc is called. This method

43

gets the cheapest stocks among the first n records in database. It has very similar

implementation to the previous getStockList() method. It differs in the SQL statement that

is executed. A nested class called CheapestStockQuery is used by this method.

class CheapestStockQuery extends MappingSqlQuery {

CheapestStockQuery(DataSource ds, int rows) {

super(ds, "SELECT DB2ADMIN.STOCK.INDEX1, DB2ADMIN.STOCK.FULLNAME,

DB2ADMIN.STOCK.VALUE1, DB2ADMIN.STOCK.UP1, DB2ADMIN.STOCK.DOWN1,

DB2ADMIN.STOCK.PREVIOUSCLOSE, DB2ADMIN.STOCK.DAYSLOWVALUE,

DB2ADMIN.STOCK.DAYSHIGHVALUE, DB2ADMIN.STOCK.TIMEOFRETRIEVAL FROM

DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.VALUE1 IN (SELECT MIN(DB2ADMIN.STOCK.VALUE1)

FROM DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.ID < "+rows+ " GROUP BY

DB2ADMIN.STOCK.INDEX1) AND DB2ADMIN.STOCK.ID < "+rows);

compile();

}

protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {

return stockDetails;}

}

This SQL statement uses a special MIN keyword to get the cheapest stocks from a given

subset of rows. The rest of the method stays same as in the previous case.

The last piece of this MVC application is the View. Two simple Java Server Pages

make up the view of the application. These pages provide the same functionality, except

that one is used to handle the cases where Spring JDBC is used, and the other one handles

the other cases. This is because Spring has structural differences compared to other

technologies. These pages list the resulting stock data in a table. They also use some of

the tags included with the Java Standard Tag Library (JSTL).

44

Chapter 4

Experimental Study of Data Access Performance

4.1 Experimental environment

Before any testing can be done, the benchmark application needs to be configured and

deployed. Like most applications, this application needs some hardware and software

resources to run. The results given further in the document are obtained using the

following environment:

Blade Station Server HS20 powered by two 3.6 GHz Intel Xeon processors, 2GB

of RAM and 2x36GB hard drives running in RAID 1 mode for maximum

performance.

Windows 2003 Server operating system.

DB2 Universal Database 8.2 – is used to hold data for stock records.

WebSphere Application Server 6.0 – using a Legacy/CLI type 2 JDBC driver

which uses OS-specific library to facilitate communication to DB2.

Gigabit connection to the outside world.

4.2 Testing process and results

The purpose of this benchmark application is to compare four different data access

technologies: JDBC, Stored Procedures, EJB and Spring JDBC. This comparison is done

by testing against a real database populated with data. The following describes the testing

process flow when using the benchmark application.

45

The user opens a browser and opens a specific JSP page. This page allows the user to

select one of eight options from a combo box:

All stock prices – JDBC is used to get all records.

Cheapest prices only – JDBC is used to get cheapest unique stocks only.

All prices (using SP) – Stored Procedures are used to get all records.

Cheapest prices (using SP) – Stored Procedures are used to get cheapest unique

stocks only.

All prices (using EJB) – Enterprise Java Beans are used to get all records.

Cheapest prices (using EJB) – Enterprise Java Beans are used to get cheapest

unique stocks only.

All prices (using Spring) – Spring JDBC is used to get all records.

Cheapest prices (using Spring) – Spring JDBC is used to get cheapest unique

stocks only.

On the same page, there is a second combo box, which allows the user to choose how

many rows of data to be included in the search. The combo box allows any number from

100 to 20000 (in increments of 100) to be selected. This selection along with the one

from the previous combo box forms a complete test case. If, for example a user selects

“All Stock Prices” from the first combo box and 300 rows from the second combo box,

then only the first 300 stocks in the database are retrieved and displayed in the table.

46

It should be clear that processing power of the hardware used for testing have great

impact on the results. This is why it is important that all testing be done using the same

machine. The results published in this document were obtained using a node of the Blade

Server Station - powered by two 3.6 GHz Intel Xeon CPU’s and 2GB of RAM.

Each of the eight cases mentioned above need be tested multiple times using different

number of rows. Cashing data on the server side and in database could also influence

results. That is why it is a good idea to run tests in such order that no two consecutive

tests will use the same data access technology and the same number of rows. Each test

will be run three times and an average value is calculated. The following spreadsheet lists

results from the tests.

Table 1 - Experiment Data

JDBC Stored Procedures EJB Spring

RowsAll

pricesCheapest

pricesAll

pricesCheapest

pricesAll

pricesCheapest

pricesAll

pricesCheapest

prices

100 Test 1 31 0 1 0 359 63 16 15

Test 2 0 0 0 0 78 0 16 16

Test 3 0 0 0 15 250 0 0 16

Average 10.33 0 0.33 5 229 21 10.67 15.67

200 Test 1 0 16 16 15 94 78 0 16

Test 2 15 15 0 16 16 16 0 15

Test 3 0 0 0 0 16 15 16 0

Average 5 10.33 5.33 10.33 42 36.33 5.33 10.33

300 Test 1 0 15 0 0 31 15 0 16

Test 2 15 16 16 0 31 15 15 0

Test 3 15 0 16 0 15 47 16 0

Average 10 10.33 10.67 0 25.67 25.67 10.33 5.33

400 Test 1 15 15 31 0 203 78 15 0

Test 2 16 0 16 0 47 31 15 16

Test 3 15 0 16 16 63 31 15 0

Average 15.33 5 21 5.33 104.33 46.67 15 5.33

47

500 Test 1 0 0 16 0 31 31 15 0

Test 2 16 0 16 16 47 31 16 0

Test 3 16 15 16 15 47 31 15 0

Average 10.67 5 16 10.33 41.67 31 15.33 0

1000 Test 1 32 0 31 16 124 62 31 16

Test 2 15 15 32 0 78 62 31 0

Test 3 125 15 31 0 93 47 15 0

Average 57.33 10 31.33 5.33 98.33 57 25.67 5.33

1500 Test 1 62 31 47 31 156 94 63 16

Test 2 32 16 47 0 125 125 47 0

Test 3 47 16 47 0 172 93 46 0

Average 47 21 47 10.33 151 104 52 5.33

2000 Test 1 63 32 63 16 187 141 47 16

Test 2 47 32 47 16 172 125 63 15

Test 3 62 31 47 0 188 125 62 0

Average 57.33 31.67 52.33 10.67 182.33 130.33 57.33 10.33

2500 Test 1 78 31 63 15 234 156 63 15

Test 2 62 31 62 16 219 157 78 16

Test 3 62 31 63 16 234 156 78 15

Average 67.33 31 62.67 15.67 229 156.33 73 15.33

3000 Test 1 109 31 78 16 375 203 78 15

Test 2 78 31 78 15 297 187 109 16

Test 3 78 31 78 16 297 188 93 16

Average 88.33 31 78 15.67 323 192.67 93.33 15.67

3500 Test 1 109 47 93 31 344 250 94 15

Test 2 94 47 93 15 328 313 109 16

Test 3 93 46 79 15 312 218 94 15

Average 98.67 46.67 88.33 20.33 328 260.33 99.00 15.33

4000 Test 1 109 47 203 16 375 250 125 15

Test 2 203 47 110 0 375 250 124 0

Test 3 109 47 94 15 375 250 125 15

Average 140.33 47 135.67 10.33 375 250 124.67 10

4500 Test 1 109 47 125 31 422 381 141 16

Test 2 125 47 125 0 406 282 203 16

Test 3 110 47 126 15 453 281 125 16

Average 114.67 47 125.33 15.33 427 314.67 156.33 16

5000 Test 1 140 47 218 31 484 344 156 15

Test 2 141 47 125 16 454 328 140 16

Test 3 126 47 125 15 563 313 140 16

Average 135.67 47 156 20.67 500.33 328.33 145.33 15.67

48

5500 Test 1 157 63 141 16 516 469 171 16

Test 2 141 63 141 15 610 328 172 31

Test 3 141 63 141 15 500 344 156 15

Average 146.33 63 141 15.33 542 380.33 166.33 20.67

6000 Test 1 156 78 172 16 563 375 172 16

Test 2 141 62 140 16 687 360 172 31

Test 3 156 63 250 16 563 390 250 15

Average 151 67.67 187.33 16 604.33 375 198 20.67

6500 Test 1 172 63 172 31 641 406 187 15

Test 2 172 79 172 15 656 406 187 15

Test 3 171 62 172 16 593 406 187 15

Average 171.67 68 172 20.67 630 406 187 15

7000 Test 1 172 63 172 16 656 422 203 31

Test 2 172 63 172 31 750 422 203 16

Test 3 172 63 172 16 640 610 282 31

Average 172 63 172 21 682 484.67 229.33 26

7500 Test 1 203 78 203 31 688 610 219 16

Test 2 204 63 187 31 844 454 218 32

Test 3 203 63 188 15 688 453 219 16

Average 203.33 68 192.67 25.67 740 505.67 218.67 21.33

8000 Test 1 203 78 219 31 756 578 234 31

Test 2 203 78 203 31 734 500 313 16

Test 3 219 78 203 16 750 532 234 16

Average 208.33 78 208.33 26 746.67 536.67 260.33 21

8500 Test 1 219 78 203 16 797 515 250 16

Test 2 219 78 219 16 812 546 250 16

Test 3 218 78 219 15 813 547 250 16

Average 218.67 78 213.67 15.67 807.33 536 250 16

9000 Test 1 234 94 219 31 844 563 250 31

Test 2 235 94 234 31 843 593 265 16

Test 3 219 93 234 31 875 578 266 16

Average 229.33 93.67 229 31 854 578 260.33 21

9500 Test 1 250 94 234 31 907 687 359 31

Test 2 250 93 234 31 891 594 281 32

Test 3 234 78 235 16 891 703 281 15

Average 244.67 88.33 234.33 26 896.33 661.33 307 26

10000 Test 1 266 93 265 31 954 641 281 15

Test 2 282 109 265 31 953 640 375 15

Test 3 265 94 250 31 1047 640 360 16

Average 271 98.67 260 31 984.67 640.33 338.67 15.33

49

10500 Test 1 281 94 281 32 1031 750 313 16

Test 2 265 93 265 15 984 656 297 16

Test 3 266 109 250 31 984 672 296 16

Average 270.67 98.67 265.33 26 999.67 692.67 302 16

11000 Test 1 282 109 266 16 1140 703 391 15

Test 2 281 93 265 31 1078 703 312 16

Test 3 265 94 281 32 1016 703 406 31

Average 276 98.67 270.67 26.33 1078 703 369.67 20.67

11500 Test 1 297 110 390 31 1078 766 406 15

Test 2 297 109 375 31 1079 735 329 32

Test 3 297 109 297 31 1125 718 328 16

Average 297 109.33 354 31 1094 739.67 354.33 21

12000 Test 1 297 125 406 32 1141 781 343 31

Test 2 422 109 312 78 1141 781 422 31

Test 3 313 110 296 32 1141 781 344 31

Average 344 114.67 338 47.33 1141 781 369.67 31

12500 Test 1 312 109 313 46 1282 812 359 16

Test 2 328 125 312 31 1250 812 360 31

Test 3 312 125 312 47 1219 844 359 16

Average 317.33 119.67 312.33 41.33 1250.33 822.67 359.33 21

13000 Test 1 344 125 328 31 1328 875 453 31

Test 2 344 126 344 47 1250 953 375 31

Test 3 328 125 328 31 1250 844 374 31

Average 338.67 125.33 333.33 36.33 1276 890.67 400.67 31

13500 Test 1 328 109 344 32 1296 874 391 31

Test 2 359 125 344 47 1313 906 391 31

Test 3 328 125 344 31 1296 906 390 32

Average 338.33 119.67 344 36.67 1301.67 895.33 390.67 31.33

14000 Test 1 437 125 437 47 1438 938 407 31

Test 2 453 140 453 47 1375 906 406 31

Test 3 359 126 360 32 1453 922 390 31

Average 416.33 130.33 416.67 42 1422 922 401 31

14500 Test 1 360 125 360 32 1485 937 516 16

Test 2 375 140 360 31 1391 1063 422 32

Test 3 469 141 360 31 1391 953 406 31

Average 401.33 135.33 360 31.33 1422.33 984.33 448 26.33

15000 Test 1 484 141 375 31 1437 1000 437 31

Test 2 390 157 374 46 1547 1140 437 16

Test 3 375 141 375 63 1563 985 421 15

Average 416.33 146.33 374.67 46.67 1515.67 1041.67 431.67 20.67

50

15500 Test 1 374 140 390 31 1500 1156 437 31

Test 2 406 156 407 47 1579 1140 531 62

Test 3 484 140 391 31 1609 1125 437 16

Average 421.33 145.33 396 36.33 1562.67 1140.33 468.33 36.33

16000 Test 1 407 140 375 31 1547 1063 532 31

Test 2 421 157 390 47 1547 1078 453 16

Test 3 406 140 500 31 1515 1063 469 31

Average 411.33 145.67 421.67 36.33 1536.33 1068 484.67 26

16500 Test 1 422 141 406 31 1578 1094 469 16

Test 2 422 156 406 47 1703 1094 468 31

Test 3 422 157 406 31 1672 1125 485 32

Average 422 151.33 406 36.33 1651 1104.33 474 26.33

17000 Test 1 531 188 406 47 1671 1125 562 31

Test 2 437 156 422 47 1735 1109 562 16

Test 3 438 156 516 31 1750 1125 485 16

Average 468.67 166.67 448 41.67 1718.67 1119.67 536.33 21

17500 Test 1 547 171 421 32 1703 1250 500 16

Test 2 453 172 438 31 1688 1250 578 16

Test 3 453 172 437 47 1719 1281 516 31

Average 484.33 171.67 432 36.67 1703.33 1260.33 531.33 21

18000 Test 1 453 172 454 31 1843 1172 594 31

Test 2 469 172 453 31 1782 1312 594 32

Test 3 453 157 437 47 1719 1188 516 16

Average 458.33 167 448 36.33 1781.33 1224 568 26.33

18500 Test 1 547 172 547 47 1922 1312 531 31

Test 2 485 187 469 31 1797 1250 625 15

Test 3 453 156 453 31 1766 1219 531 16

Average 495 171.67 489.67 36.33 1828.33 1260.33 562.33 20.67

19000 Test 1 484 172 454 31 1859 1282 547 16

Test 2 468 172 578 31 1843 1375 532 31

Test 3 579 171 469 47 1860 1250 609 32

Average 510.33 171.67 500.33 36.33 1854 1302.33 562.67 26.33

19500 Test 1 500 171 578 47 1907 1344 546 15

Test 2 500 172 578 31 1891 1297 547 31

Test 3 500 171 484 47 1922 1281 547 31

Average 500 171.33 546.67 41.67 1906.67 1307.33 546.67 25.67

20000 Test 1 500 187 578 31 1953 1343 641 93

Test 2 515 187 484 31 2031 1453 578 16

Test 3 593 188 484 32 2047 1454 656 32

Average 536 187.33 515.33 31.33 2010.33 1416.67 625 47

51

It is not easy to analyze data from a table like this one. A better way to understand the

data is to have it represented graphically. The chart from Figure 6 - Result Data Graph is

a graphical representation of the same data in a human readable format.

52D

ata Access P

erformance

0.00

200.00

400.00

600.00

800.00

1000.00

1200.00

1400.00

1600.00

1800.00

2000.00

100300

5001500

25003500

45005500

65007500

85009500

1050011500

1250013500

1450015500

1650017500

1850019500

number of stock records

tim e m s

All prices(JD

BC

)C

heapest prices(JDB

C)

All prices(S

P)

Cheapest prices(S

P)

All prices(E

JB)

Cheapest prices(E

JB)

All prices(S

pring)C

heapest prices(Spring)

53

Figure 6 - Result Data Graph

The graph from Figure 6 - Result Data Graph represents results obtained by

experiments. Eight curves represent each of the eight cases that the benchmark

application has. One of the axes shows how much time it took to do the operation and the

other axis shows how many rows were used.

For simplicity, the following suffixes are used throughout the text: “min” - for

cheapest stocks and “all” - for all stocks. These are used in combination with prefixes for

the appropriate technologies: “JDBC” - for Java Database Connectivity, “SP” – for

Stored Procedures, “EJB” – for Enterprise Java Beans and “Spring” – for the Spring

JDBC API.

At first glance, it is obvious from Figure 6 - Result Data Graph how the curves divert

from each other as the amount of data used grows. Some technologies perform a lot better

than others as the amount of data grows. When using only a 100 records, all curves are

about the same level except for the EJB-min access curve. This is probably due to some

initialization process that takes time in the EJB container. As the record number grows to

200 all curves stick together at the same level. At 300 hundred records the EJB-min curve

splits above the other curves and keeps going higher as the amount of data grows. Until

around 600 records, the other curves keep together and then they divert.

The SP-min curve stays at a low value until the end with just a slight increase. The

Spring-min curve follows up throughout the graph. This solution is also fast because the

SQL statement used in Spring-min does the processing at database level using a MIN

keyword. If we wanted to do other processing, Spring JDBC would have had to do it

54

outside the database and it would be slower. Using a stored procedure to get the cheapest

stocks is definitely fast, but it is not always necessary. Sometimes all processing can be

done at database level using only SQL keywords and without a stored procedure.

The JDBC-min curve is at the same level with SP-min up to around 3000 records, and

then it diverts to higher values. This is because our JDBC code does the minimum-search

processing out from the database. Up to 3000 records, this processing overhead is too

small to make an impact, but it makes a difference when using large amount of data.

The JDBC-all and SP-all curves overlap throughout the whole graph. Therefore, it

does not really make a difference whether JDBC or a Stored Procedure is used unless

there is some processing involved. In this case there is no processing, just listing the

records from the database. Spring-all follows these two curves up to about 5000 rows,

then diverts to a little bit higher value, and runs in parallel with them through most of the

graph, intersecting them occasionally. It is obvious from the graph that the Spring JDBC

introduces a small processing overhead, but this overhead stays constant and insignificant

even with large amount of data.

Next, high above all previously discussed curves on the graph is the EJB-min curve.

Obviously, this one performs worse than competitor technologies - JDBC-min, SP-min

and Spring-min. An EJB-QL query is used to get the cheapest stocks in this case.

Because EJB-QL does not have a way to specify that only the cheapest stocks be

retrieved at database level, a workaround has to be used. The workaround is to get all the

stocks using a proprietary EJB-QL query, and then do the processing using Java code

55

outside the database. Of course, this takes more time compared to when all processing

happened at database level.

There is additional processing involved when returning results from EJB-QL queries

in an entity bean. In the benchmark application, stocks are retrieved using a finder

method. This method executes an EJB-QL query that returns a Collection object

populated with StockLocal objects. Each of these is an object representation of a single

row in the database. What actually happens behind scenes is the EJB container executes a

SQL statement, gets results back in a ResultSet object, iterates through it and creates a

StockLocal object at each loop, adding all of them into one Collection object. This takes

up CPU time and memory. The interface object (StockLocal) is a Java interface class that

contains a setter and getter method for each stock attribute and relationship (if any). This

interface may also contain signatures for methods that are available to the clients. All this

functionality takes up memory, so the Collection object grows in size much faster than a

simple ResultSet object, resulting with larger JVM (Java Virtual Machine) heap size.

When using an entity bean to access a database, a client should not be allowed to

interact with the entity bean directly. Usually there is a session bean between the client

and the entity bean. The client interacts with the session bean, which then propagates the

call to an entity bean. This is mainly for security reasons and simpler JTA transaction

management, but it also introduces some processing overhead.

Above all other curves on the graph is the EJB-all curve. Only for small amount of

data (up to 300 stock records), the curve follows up with other curves. Then it diverts and

grows more and more as the amount of data grows. Obviously, it performs much slower

56

than the competition – JDBC-all, SP-all and Spring-JDBC. Here too, as with EJB-min,

finder method with the same EJB-QL query is used. The different bit is that all stocks are

returned without filtering the cheapest ones only.

At first glance, one would expect to see EJB-min perform slower than EJB-all, but the

tests prove the opposite. The only explanation for this is that it takes more time to

populate a Collection object with 20000 StockLocal instances, than to pick the cheapest

of all StockLocal objects and put only those in the Collection object.

All these factors contribute to EJB being slower than the other technologies, and that

puts the EJB-min and EJB-all curves above the other curves on the graph.

57

Chapter 5

Conclusion

It is clear that Enterprise Java Beans is not among the fastest database access

technology available. It is in fact a slow one compared to the others since there is

additional processing happening behind the scenes. However, it offers great Object-to-

Relational mapping and it can reduce development time significantly. It simplifies the

process of maintaining complex relationships, allowing developers to focus on the

business logic.

An entity bean has a complex life cycle including features such as cashing and bean

passivation. Bean cashing might improve performance in applications in which data

access has low intensity and most data is reused multiple times. If the application uses

large amounts of data at once, these techniques would become inefficient because they

take up a lot of memory.

In most applications, a better choice is to use Stored Procedures. Most databases

support them nowadays. This way all business processing happens within the database

for maximum performance. This also simplifies maintenance. If a business rule needs to

change, it only needs to change once, at database level, without any changes to the client

code.

58

JDBC might be the right choice when business processing needs to stay out of the

database. Using a stored procedure is advantageous only when some processing happens

at database level. Otherwise, it performs same as a JDBC statement. In cases where it is

possible to do the entire database processing using a simple SQL statement, (potentially

using some of the keywords) it would be simpler to use JDBC. If all processing happens

at the database level, it will perform at the same speed as when using a corresponding

stored procedure.

Spring JDBC offers an abstraction layer API over JDBC. It performs quite well

introducing just a small performance loss. Other than that, it simplifies development a lot

by removing all of the boilerplate code that goes with JDBC code. It also provides an

exception translation mechanism that translates vendor specific error messages into

standard messages. In addition, since all configuration details are in xml configuration

files instead of hard coded, it is very flexible. Spring JDBC also offers the same

transaction support as EJB except for one. It does not support transactions that span

across multiple remote machines. EJB is the only technology that has this functionality as

for now.

Spring JDBC is a good choice if the application can afford a small performance loss.

In return, it gives back a lot of flexibility and faster development.

59

Benchmark Application Installation Guide

In order to run the benchmark application, these steps need to be done.

- create database infrastructure

- configure resources on the server

- deploy the application & generate test data

The following text assumes a machine that has WebSphere Application Server 6.0 and

DB2 UDB 8.2 installed on a Windows OS.

Creating database infrastructure

First of all a database must be created to hold the data. In this application, the

database is named DBTEST. One way to do this is by using the DB2 command

console.

Start the DB2 command console by navigating to StartAll ProgramsIBM

DB2Command Line ToolsCommand Line Processor. The DB2 console

window opens.

60

Type “create database DBTEST” and hit Enter. Depending on the machine, it

may take up to 2-3 minutes to create the database.

Type “quit” and hit Enter to terminate the connection. Then close the window.

The next step is to create database infrastructure. To make the process simpler there is a

script file, named Tables.ddl, included with the distribution CD. The DB2 Command

Center will be used to run this script.

From the Windows desktop, navigate to StartAll ProgramsIBM

DB2Command Line ToolsCommand Center. Click the Script tab. Navigate to

ScriptImport from the menu bar. From the dialog, choose your System name,

locate the Tables.ddl file and click OK. Finally, execute the script by selecting

ScriptExecute. The script should execute successfully. Close the Command

Center utility.

At this point there is a database with a schema and some empty tables in it. Later on, the

benchmark application will be used to populate these tables with data.

Setting up server resources

The benchmark application needs a data source to communicate with the database.

Therefore, a data source resource must be configured and maintained by WAS. The

application looks up this resource under the JNDI name jdbc/bank. Therefore, the data

source must be bound to JNDI under this name. The WebSphere console application has

an easy way to accomplish this.

61

Make sure WAS is running, otherwise the console application will not work.

WAS can be started by clicking StartAll ProgramsIBM

WebSphereApplication Server v6.0defaultStart Server (This step assumes

running WAS as a standalone server, not the integrated testing environment that

comes with Rational Application Developer).

Start the WAS console by opening a browser to the following URL:

https://yourhost:9043/ibm/console/. In the URL, yourhost is the machine where

WAS is running. The port might be different and it is set to 9043 by default. This

points to the WAS console login screen. Unless security is enabled, it does not

require a password.

Make sure WAS knows where to look for a DB2 JDBC driver. Navigate to

EnvironmentWebSphere VariablesDB2_JDBC_Driver_Path and set this

variable value to the folder where db2java.zip is located. In a default installation,

this value should be C:/Program Files/IBM/SQLLIB/Java.

The next step is to configure the JDBC provider. Click ResourcesJDBC

ProvidersNew. Select “DB2”, “DB2 Legacy CLI-based Type 2 JDBC Driver”

and “Connection pool data source” from the three combo boxes. Click Next and

then OK on the last dialog. Save the changes when asked.

Finally, a JDBC data source needs to be created to talk to the DBTEST database.

Click the newly created provider - “DB2 Legacy CLI-based Type 2 JDBC Driver”

and navigate to DatasourcesNew. Enter jdbc/bank in the JNDI name field and

62

DBTEST in the Database name field. Save the changes and then test the

connection using the provided button. There should be a message saying that the

connection was successful. Otherwise, there is a problem.

Application deployment & generating test data

Now that the server has everything the application needs, the application can be

deployed. The StockEAR.ear file provided with the distribution CD will be used for this.

The easiest way to deploy the application is by using the WAS console.

Once again make sure WAS is running and open the browser to the WAS console

URL, https://somehost:9043/ibm/console/.

Enter the console and click ApplicationsInstall New Applications. Locate the

StockEAR.ear file and click Next. Keep clicking Next about 10 more times and at

the end click Finish. There will be a message saying that the StockEAR

application was installed successfully. Click Save to Master Configuration and

then Save. At this point the application is installed but it needs to be started.

From the list of installed applications, select the check box in front of StockEAR

and click Start.

The application is started but it still doesn’t have any data to work with. In order

to populate the database with data, browse to this url:

http://somehost:9080/Stock/populateData. It might take a couple of minutes for

this to finish. Once it is done, the data is written in to the database. There is no

need to repeat this process ever again unless the tables are deleted.

63

Finally the application is ready for testing and may be accessed from a browser using the

following url: http://somehost:9080/Stock/stockList, where somehost is the name of the

machine (possibly localhost if accessed from the same machine). The port number may

vary too, but WebSphere listens for HTTP traffic on port 9080 by default.

Setting up the project with RAD

This step is not required for running the benchmark application. However, it is

required in order to see the source code of the application. As mentioned in Experimental

environment, Rational Application Developer is the IDE used for the application.

Therefore, it needs to be installed on the machine in order to proceed with the following

steps.

Start Rational Application Developer. On a Windows machine select StartAll

ProgramsIBM RationalIBM Rational Application Developer V6.0

Rational Application Developer.

Import the application source code in to the workspace. With the distribution CD,

there is a StockEAR.zip file included. This file contains source code and other files

used by the application organized in a directory structure. Click FileImport...

From the dialog select Project Interchange and click Next. Locate the

StockEAR.zip file. Click Select All and then Finish. This imports the benchmark

application in RAD’s workspace. In the Project Explorer tree, located left by

default, expand Dynamic Web Projects and there is the Stock folder containing

our web application. In the same tree, expand EJB Projects and the StockEJB

folder containing the EJB part of the application pops up.

64

Like many IDE tools, RAD also allows deploying applications from within the tool

directly. Unlike other tools, RAD includes an integrated testing environment of the

WebSphere Application Server. The user may deploy the benchmark application to the

integrated server or a standalone instance. The integrated server is configured in the

Servers view by default when RAD is installed. If the application needs to be deployed

on a different server, RAD needs to be informed about the type and location of this

server. The following step describes setting up RAD to work with the Blade Station

Server running at Pace University. This machine has WebSphere Application Server v6.0

installed and running.

In order to be able to reach the server, a client machine (with RAD installed) must

be inside the Pace University network. This can also be achieved using a VPN

client to get in the network. For more info on setting up the Pace VPN client, see

http://appserv.pace.edu/emplibrary/VPNWin2000RR070803.pdf. Once in the

network, start RAD and open the Servers view, right click and choose

NewServer from the popup menu. A new dialog box opens. In the Host name

field type the IP address of our test server - 172.20.138.41. Select WebSphere v6.0

Server as the server type and click Next. Click Detect. RAD detects the server

automatically using the default port values. Click Finish and the new server will

appear in the Servers view.

Notice what the status of the new server is. It needs to be started before running

the application. RAD might show status - stopped although the server is in fact

running. Sometimes it needs a little push to catch up. Right click the server,

choose Add and remove projects... from the popup menu. RAD picks up the

65

server state again and it should say started this time. If not, the server might need

to be started indeed. A WAS instance cannot be started remotely from a client

machine running RAD. This can only be done onsite from the Blade Station

Server console itself. RAD may only stop or restart the server, so be careful not to

stop the server.

Having configured the remote running WAS instance it is simple to deploy an application. This remote instance is preconfigured with the required resources and data as described in Setting up server resources

. Right click the server from the Servers view and select Add and Remove

Projects... from the popup menu. A new dialog pops up. Select StockEAR from

the Available projects list, click Add and then Finish. RAD deploys the

application and if everything goes fine, the benchmark application can be

accessed through a browser using the following URL:

http://172.20.138.41:9080/Stock/stockList .

Software tools used for the application

IBM’s Rational Application Developer version 6.0 for Windows XP 32 bit edition

– a Java IDE based on the Eclipse platform, specialized for development on the

Websphere Application Server.

IBM’s DB2 Universal Database Version 8.1 – this is the database where stock

records will be stored.

IBM’s WebSphere Application Server version 6.0 – A robust J2EE compliant

application server that is used to run the application.

66

All of the above-mentioned software is available for a free 60-day evaluation from

http://www14.software.ibm.com/webapp/download/home.jsp?s=p. You will be asked to

fill out a free IBM registration form. The files are quite large so plan using a high-speed

internet connection and about 10 GB of hard drive space.

The hardware behind the code

IBM’s Blade Center Station Server – 10 clusters with two 3.6 GHz CPU’s and

2GB of RAM memory each. Although there were 10 clusters available, at the

time of testing only two clusters were used. One of the clusters runs the

WebSphere Application Server and a second cluster runs as a dedicated database

machine using DB2.

A Compaq Presario R3000 laptop – the client machine that was used to run

Rational Application Developer and deploy the application remotely on the Blade

Server.

Key Source Files

Dispatcher.java:

package com.dimitri.servlets;

import java.io.IOException;

import java.util.Collection;

import javax.servlet.Servlet;

67

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import com.dimitri.beans.TestData;

public class Dispatcher extends HttpServlet implements Servlet {

public static final int ALL_PRICES=0;

public static final int CHEAPEST_PRICES=1;

public static final int ALL_PRICES_SP=2;

public static final int CHEAPEST_PRICES_SP=3;

public static final int ALL_PRICES_EJB=4;

public static final int CHEAPEST_PRICES_EJB=5;

public static final int ALL_PRICES_SPRING=6;

public static final int CHEAPEST_PRICES_SPRING=7;

protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {

performTask(arg0, arg1);

}

protected void doPost(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException {

performTask(arg0, arg1);

}

private void performTask(HttpServletRequest request, HttpServletResponse response)

{ String selectedScreen = request.getServletPath().trim();

String nextPage=null;

Collection stocks=null;

boolean springpage=false;

if(selectedScreen.equals("/stockList"))

{ String selectedOption=request.getParameter("selectedScreen");

if(selectedOption==null)selectedOption="0";

String rows=request.getParameter("rows");

if(rows==null)rows="1000";

long start=System.currentTimeMillis();

switch(Integer.parseInt(selectedOption))

68

{case ALL_PRICES: stocks=TestData.getDataJDBC(rows);break;

case CHEAPEST_PRICES: stocks=TestData.getCheapestDataJDBC(rows);break;

case ALL_PRICES_SP: stocks=TestData.getDataSP(rows);break;

case CHEAPEST_PRICES_SP: stocks=TestData.getCheapestDataSP(rows);break;

case ALL_PRICES_EJB: TestData testData=new TestData(); stocks=testData.getDataEJB(rows);break;

case CHEAPEST_PRICES_EJB: testData=new TestData(); stocks=testData.getCheapestDataEJB(rows);break;

case ALL_PRICES_SPRING:

case CHEAPEST_PRICES_SPRING: springpage=true;

try {request.getRequestDispatcher("/stockList.htm").forward(request, response);} catch (Exception ex) {

System.out.println("i crashed here "+ex.getMessage());};break;}

long end=System.currentTimeMillis();

request.setAttribute("stocks",stocks);

request.setAttribute("duration",new Long((end-start)).toString());

nextPage="/stockList.jsp";

}else if(selectedScreen.equals("/populateData"))

{TestData.generateDataInDatabase();

TestData.generateDataForStockEJB();}

if(springpage)return;

try {request.getRequestDispatcher(nextPage).forward(request, response);}

catch (Exception ex) {System.out.println("i crashed");}

}

}

web.xml:

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="WebApp_ID" 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>Stock</display-name>

<servlet>

<description>

the servlet that controls everything else</description>

<display-name>Dispatcher</display-name>

<servlet-name>Dispatcher</servlet-name>

69

<servlet-class>com.dimitri.servlets.Dispatcher</servlet-class>

</servlet>

<servlet>

<servlet-name>stocks</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<load-on-startup>2</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>Dispatcher</servlet-name>

<url-pattern>/stockList</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>Dispatcher</servlet-name>

<url-pattern>/populateData</url-pattern>

</servlet-mapping>

<servlet-mapping>

<servlet-name>stocks</servlet-name>

<url-pattern>*.htm</url-pattern>

</servlet-mapping>

<welcome-file-list>

<welcome-file>index.html</welcome-file>

<welcome-file>index.htm</welcome-file>

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

<welcome-file>default.html</welcome-file>

<welcome-file>default.htm</welcome-file>

<welcome-file>default.jsp</welcome-file>

</welcome-file-list>

<ejb-ref id="EjbRef_1152990947859">

<description>

</description>

<ejb-ref-name>ejb/StockManager</ejb-ref-name>

<ejb-ref-type>Session</ejb-ref-type>

70

<home>stocks.ejb.StockManagerHome</home>

<remote>stocks.ejb.StockManager</remote>

<ejb-link>StockEJB.jar#StockManager</ejb-link>

</ejb-ref>

<ejb-local-ref id="EJBLocalRef_1152994845609">

<description>

</description>

<ejb-ref-name>ejb/StockManager_local</ejb-ref-name>

<ejb-ref-type>Session</ejb-ref-type>

<local-home>stocks.ejb.StockManagerLocalHome</local-home>

<local>stocks.ejb.StockManagerLocal</local>

<ejb-link>StockEJB.jar#StockManager</ejb-link>

</ejb-local-ref>

<resource-env-ref id="ResourceEnvRef_1149639086468">

<description>

this is a reference to an actual db2 datasource on the server</description>

<resource-env-ref-name>mybank</resource-env-ref-name>

<resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>

</resource-env-ref>

</web-app>

TestData.java:

package com.dimitri.beans;

import java.rmi.RemoteException;

import java.sql.CallableStatement;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import java.util.Collection;

71

import java.util.Date;

import java.util.Hashtable;

import java.util.Iterator;

import java.util.Random;

import java.util.Vector;

import javax.naming.Context;

import javax.naming.InitialContext;

import javax.naming.NamingException;

import javax.sql.DataSource;

import stocks.ejb.StockManager;

import stocks.ejb.StockManagerHome;

import stocks.ejb.StockManagerLocal;

import stocks.ejb.StockManagerLocalHome;

import com.dimitri.exceptions.DataSourceException;

import com.ibm.etools.service.locator.ServiceLocatorManager;

public class TestData {

static String[] indexes = { "ABC", "RVD", "RKM", "AFD", "FDU", "RMU",

"RPA", "WMA", "AMP", "ITL", "ROM", "JOE", "BMW", "JUM" };

static String[] fullNames = { "American Brave Consumers","Record Video Drive", "Remote Copy Machines",

"Aggravated Female Drivers", "French Driving Union","Remote Motor Union", "Radical Phillipino Association",

"Federal Bussines Association", "Antic Military Police","Italian Frozen Foods", "The Olive Merchant", "Joe Traders",

"Bavarian Motor Machines", "Jumbo Underground Mergers" };

private final static Class STATIC_StockManagerHome_CLASS = StockManagerHome.class;

static public Vector getStockData() {

Vector stocks = new Vector(10);

Random generator = new Random();

StockDetails stockDetails = null;

for (int i = 0; i < 20000; i++)

{int ranomInt = generator.nextInt(indexes.length);

double randomValue = generator.nextDouble() * 1000000;

double up = generator.nextDouble() * 100;

double down = -generator.nextDouble() * 100;

72

double previousClose = -generator.nextDouble() * 1000000;

double daysLowValue = -generator.nextDouble() * 1000000;

double daysHighValue = -generator.nextDouble() * 1000000;

Date timeOfRetrieval = new Date(System.currentTimeMillis());

stockDetails = new StockDetails(indexes[ranomInt], fullNames[ranomInt], randomValue, up, down, previousClose,

daysLowValue, daysHighValue, timeOfRetrieval);

stocks.add(stockDetails);}

return stocks;

}

public static void main(String[] args)

{generateDataInDatabase();

generateDataForStockEJB();}

static DataSource dataSource = null;

static Connection connection = null;

private final static String STATIC_StockManagerHome_REF_NAME = "ejb/StockManager";

private final static String STATIC_StockManagerLocalHome_REF_NAME = "ejb/StockManager_local";

public static DataSource getDataSource() throws DataSourceException {

if (dataSource != null) {return dataSource;}

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,"com.ibm.websphere.naming.WsnInitialContextFactory");

InitialContext ctx = null;

try{ctx = new InitialContext(env);} catch (NamingException e) {e.printStackTrace();}

try{dataSource = (DataSource) ctx.lookup("java:comp/env/mybank");}

catch (NamingException e1) {e1.printStackTrace();}

if (dataSource == null) throw new DataSourceException("Datasource not found");

else return dataSource;}

public static Connection getConnection() {

try {connection = getDataSource().getConnection();}

catch (SQLException e) {e.printStackTrace();}

catch (DataSourceException e) {e.printStackTrace();}

return connection;}

public static void close(Connection conn) {

73

if (conn != null)

{try {conn.close();} catch (Exception e) {e.printStackTrace();}}

}

public static void close(Statement stmt) {

if (stmt != null)

{try {stmt.close();} catch (Exception e) {e.printStackTrace();}}

}

public static void close(ResultSet rset) {

if (rset != null) {

try {rset.close();} catch (Exception e) {e.printStackTrace();}

}

}

public static void generateDataInDatabase() {

PreparedStatement preparedStatement = null;

Vector stocksVector = getStockData();

Iterator iterator = stocksVector.iterator();

try { preparedStatement = getConnection().prepareStatement( "INSERT INTO DB2ADMIN.STOCKS (INDEX,

FULLNAME, VALUE, UP, DOWN, PREVIOUSCLOSE,"+ "DAYSLOWVALUE, DAYSHIGHVALUE,

TIMEOFRETRIEVAL) VALUES "+ "(?,?,?,?,?,?,?,?,?)");

while (iterator.hasNext()) {

StockDetails stockDetails = (StockDetails) iterator.next();

preparedStatement.setString(1, stockDetails.getIndex());

preparedStatement.setString(2, stockDetails.getFullName());

preparedStatement.setDouble(3, stockDetails.getValue());

preparedStatement.setDouble(4, stockDetails.getUp());

preparedStatement.setDouble(5, stockDetails.getDown());

preparedStatement.setDouble(6, stockDetails.getPreviousClose());

preparedStatement.setDouble(7, stockDetails.getDaysLowValue());

preparedStatement.setDouble(8, stockDetails.getDaysHighValue());

preparedStatement.setDate(9, new java.sql.Date(stockDetails.getTimeOfRetrieval().getTime()));

preparedStatement.executeUpdate();

}

74

} catch (SQLException e) {e.printStackTrace();}

finally {close(preparedStatement);

close(connection);}

}

public static void generateDataForStockEJB() {

PreparedStatement preparedStatement = null;

Vector stocksVector = getDataJDBC("20000");

Iterator iterator = stocksVector.iterator();

try {preparedStatement = getConnection().prepareStatement("INSERT INTO DB2ADMIN.STOCK (ID, INDEX1,

FULLNAME, VALUE1, UP1, DOWN1, PREVIOUSCLOSE," + "DAYSLOWVALUE, DAYSHIGHVALUE,

TIMEOFRETRIEVAL) VALUES "+ "(?,?,?,?,?,?,?,?,?,?)");

int i = 0;

while (iterator.hasNext()) {

StockDetails stockDetails = (StockDetails) iterator.next();

preparedStatement.setInt(1, i);

preparedStatement.setString(2, stockDetails.getIndex());

preparedStatement.setString(3, stockDetails.getFullName());

preparedStatement.setDouble(4, stockDetails.getValue());

preparedStatement.setDouble(5, stockDetails.getUp());

preparedStatement.setDouble(6, stockDetails.getDown());

preparedStatement.setDouble(7, stockDetails.getPreviousClose());

preparedStatement.setDouble(8, stockDetails.getDaysLowValue());

preparedStatement.setDouble(9, stockDetails.getDaysHighValue());

preparedStatement.setDate(10, new java.sql.Date(stockDetails.getTimeOfRetrieval().getTime()));

preparedStatement.executeUpdate();

i++;}

} catch (SQLException e) {e.printStackTrace();} finally

{close(preparedStatement);

close(connection);}

}

public static Vector getDataJDBC(String howMany) {

PreparedStatement preparedStatement = null;

75

Vector stocksVector = new Vector(2000);

ResultSet resultSet = null;

int rows;

try {rows=Integer.parseInt(howMany);} catch (NumberFormatException e1) {return null;}

try {preparedStatement = getConnection().prepareStatement("SELECT * FROM DB2ADMIN.STOCKS FETCH

FIRST "+rows+" ROWS ONLY");

resultSet = preparedStatement.executeQuery();

StockDetails stockDetails = null;

while (resultSet.next())

{stockDetails = new StockDetails(resultSet.getString(1), resultSet.getString(2), resultSet.getDouble(3),

resultSet.getDouble(4), resultSet.getDouble(5), resultSet.getDouble(6), resultSet.getDouble(7),

resultSet.getDouble(8), resultSet.getDate(9));

stocksVector.add(stockDetails);

}

} catch (SQLException e) {e.printStackTrace();

} finally

{ close(resultSet);

close(preparedStatement);

close(connection);}

return stocksVector;

}

public static Vector getDataSP(String howMany) {

CallableStatement callableStatement = null;

Vector stocksVector = new Vector(2000);

int rows;

try {rows=Integer.parseInt(howMany);}

catch (NumberFormatException e1) {return null;}

ResultSet resultSet = null;

try{callableStatement = getConnection().prepareCall("{call DB2ADMIN.getStocksLimited(?)}");

callableStatement.setInt(1,rows);

resultSet = callableStatement.executeQuery();

StockDetails stockDetails = null;

76

while (resultSet.next()) {

stockDetails = new StockDetails(resultSet.getString(1), resultSet.getString(2), resultSet.getDouble(3),

resultSet.getDouble(4), resultSet.getDouble(5), resultSet.getDouble(6), resultSet.getDouble(7),

resultSet.getDouble(8), resultSet.getDate(9));

stocksVector.add(stockDetails);

}

} catch (SQLException e) {e.printStackTrace();

} finally {close(resultSet);

close(callableStatement);

close(connection);}

return stocksVector;}

public static Vector getCheapestDataJDBC(String howMany) {

PreparedStatement preparedStatement = null;

Vector stocksVector = new Vector(20);

ResultSet resultSet = null;

int rows;

try {rows=Integer.parseInt(howMany);} catch (NumberFormatException e1) {return null;}

try {preparedStatement = getConnection().prepareStatement( "SELECT * FROM DB2ADMIN.STOCKS FETCH

FIRST "+rows+" ROWS ONLY");

resultSet = preparedStatement.executeQuery();

StockDetails stockDetails = null;

String index = null;

while (resultSet.next()) {

index = resultSet.getString(1);

boolean exists = false;

int i;

for (i = 0; i < stocksVector.size(); i++) {

StockDetails existingStockDetails = (StockDetails) stocksVector.elementAt(i);

if (existingStockDetails.getIndex().equals(index)) {

exists = true;

if (existingStockDetails.getValue() > resultSet .getDouble(3)) {

stocksVector.remove(existingStockDetails);

77

stocksVector.add(new StockDetails(index, resultSet.getString(2), resultSet.getDouble(3), resultSet.getDouble(4),

resultSet.getDouble(5), resultSet.getDouble(6), resultSet.getDouble(7), resultSet.getDouble(8),

resultSet.getDate(9)));}

break;

}

}

if (!exists)

{stockDetails = new StockDetails(index, resultSet.getString(2), resultSet.getDouble(3), resultSet.getDouble(4),

resultSet.getDouble(5), resultSet.getDouble(6), resultSet.getDouble(7), resultSet.getDouble(8), resultSet.getDate(9));

stocksVector.add(stockDetails);}

}

} catch (SQLException e) {e.printStackTrace();} finally {

close(resultSet);

close(preparedStatement);

close(connection);}

return stocksVector;

}

public static Vector getCheapestDataSP(String howMany)

{CallableStatement preparedStatement = null;

Vector stocksVector = new Vector(2000);

ResultSet resultSet = null;

int rows;

try {rows=Integer.parseInt(howMany);} catch (NumberFormatException e1) {return null;}

try {preparedStatement =

getConnection().prepareCall("{call DB2ADMIN.GETCHEAPESTSTOCKSLIMITED(?)}");

preparedStatement.setInt(1,rows);

resultSet = preparedStatement.executeQuery();

StockDetails stockDetails = null;

while (resultSet.next()) { stockDetails = new StockDetails(resultSet.getString(1), resultSet.getString(2),

resultSet.getDouble(3), resultSet.getDouble(4), resultSet.getDouble(5), resultSet.getDouble(6),

resultSet.getDouble(7), resultSet.getDouble(8), resultSet.getDate(9));

stocksVector.add(stockDetails);

78

}

} catch (SQLException e) {e.printStackTrace(); } finally

{close(resultSet);

close(preparedStatement);

close(connection);}

return stocksVector;

}

protected StockManager createStockManager() {

StockManagerHome aStockManagerHome = (StockManagerHome) ServiceLocatorManager.

getRemoteHome(STATIC_StockManagerHome_REF_NAME, STATIC_StockManagerHome_CLASS);

try {

if (aStockManagerHome != null) return aStockManagerHome.create();

} catch (javax.ejb.CreateException ce) {ce.printStackTrace();} catch (RemoteException re) {

re.printStackTrace();}

return null;

}

public Collection getDataEJB(String howMany) {

int rows;

try {rows=Integer.parseInt(howMany);} catch (NumberFormatException e1) {return null;}

StockManagerLocal aStockManagerLocal = createStockManagerLocal();

return aStockManagerLocal.getAllStocksLimited(new Integer(rows));

}

public Collection getCheapestDataEJB(String howMany) {

int rows;

try {rows=Integer.parseInt(howMany);} catch (NumberFormatException e1) {return null;}

StockManagerLocal aStockManagerLocal = createStockManagerLocal();

return aStockManagerLocal.getCheapestStocksLimited(new Integer(rows));

}

private final static Class STATIC_StockManagerLocalHome_CLASS = StockManagerLocalHome.class;

protected StockManagerLocal createStockManagerLocal() {

StockManagerLocalHome aStockManagerLocalHome = (StockManagerLocalHome) ServiceLocatorManager

.getLocalHome(STATIC_StockManagerLocalHome_REF_NAME, STATIC_StockManagerLocalHome_CLASS);

79

try {if (aStockManagerLocalHome != null) return aStockManagerLocalHome.create();

} catch (javax.ejb.CreateException ce) {ce.printStackTrace();}

return null;

}

}

StockDetails:

package com.dimitri.beans;

import java.io.Serializable;

import java.util.Date;

public class StockDetails implements Serializable {

private String index;

private String fullName;

private double value;

private double up;

private double down;

private double previousClose;

private double daysLowValue;

private double daysHighValue;

private Date timeOfRetrieval;

public StockDetails() {}

public StockDetails(String index, String fullName, double value, double up, double down, double previousClose,

double daysLowValue, double daysHighValue, Date timeOfRetrieval)

{this.index = index;

this.fullName = fullName;

this.value = value;

this.up = up;

this.down = down;

this.previousClose = previousClose;

this.daysLowValue = daysLowValue;

this.daysHighValue = daysHighValue;

80

this.timeOfRetrieval = timeOfRetrieval;}

public double getDaysHighValue() {return daysHighValue;}

public void setDaysHighValue(double daysHighValue) {this.daysHighValue = daysHighValue;}

public double getDaysLowValue() {return daysLowValue;}

public void setDaysLowValue(double daysLowValue) {this.daysLowValue = daysLowValue;}

public double getDown() {return down;}

public void setDown(double down) {this.down = down;}

public String getFullName() {return fullName;}

public void setFullName(String fullName) {this.fullName = fullName;}

public String getIndex() {return index;}

public void setIndex(String index) {this.index = index;}

public double getPreviousClose() {return previousClose;}

public void setPreviousClose(double previousClose) {this.previousClose = previousClose;}

public Date getTimeOfRetrieval() {return timeOfRetrieval;}

public void setTimeOfRetrieval(Date timeOfRetrieval) {this.timeOfRetrieval = timeOfRetrieval;}

public double getUp() {return up;}

public void setUp(double up) {this.up = up;}

public double getValue() {return value;}

public void setValue(double value) {this.value = value;}

}

GETSTOCKSLIMITED.java:

package PKG60812103537421;

import java.sql.*;

public class GETSTOCKSLIMITED {

public static void gETSTOCKSLIMITED ( int rows, ResultSet[] rs1 ) throws SQLException, Exception

{Connection con = DriverManager.getConnection("jdbc:default:connection");

PreparedStatement stmt = null;

boolean bFlag;

String sql;

sql = "SELECT " + " DB2ADMIN.STOCK.INDEX1, "

+ " DB2ADMIN.STOCK.FULLNAME, "

81

+ " DB2ADMIN.STOCK.VALUE1, "

+ " DB2ADMIN.STOCK.UP1, "

+ " DB2ADMIN.STOCK.DOWN1, "

+ " DB2ADMIN.STOCK.PREVIOUSCLOSE, "

+ " DB2ADMIN.STOCK.DAYSLOWVALUE, "

+ " DB2ADMIN.STOCK.DAYSHIGHVALUE, "

+ " DB2ADMIN.STOCK.TIMEOFRETRIEVAL"

+ " FROM"

+ " DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.ID < "+rows;

stmt = con.prepareStatement( sql );

bFlag = stmt.execute();

rs1[0] = stmt.getResultSet();

}

}

GETCHEAPESTSTOCKSLIMITED.java:

package PKG6081210402578;

import java.sql.*;

public class GETCHEAPESTSTOCKSLIMITED {

public static void gETCHEAPESTSTOCKSLIMITED ( int rows, ResultSet[] rs1 ) throws SQLException, Exception

{Connection con = DriverManager.getConnection("jdbc:default:connection");

PreparedStatement stmt = null;

boolean bFlag;

String sql;

sql = "SELECT DB2ADMIN.STOCK.INDEX1, "

+ " DB2ADMIN.STOCK.FULLNAME, "

+ " DB2ADMIN.STOCK.VALUE1, "

+ " DB2ADMIN.STOCK.UP1, "

+ " DB2ADMIN.STOCK.DOWN1, "

+ " DB2ADMIN.STOCK.PREVIOUSCLOSE, "

+ " DB2ADMIN.STOCK.DAYSLOWVALUE, "

82

+ " DB2ADMIN.STOCK.DAYSHIGHVALUE, "

+ " DB2ADMIN.STOCK.TIMEOFRETRIEVAL"

+ " FROM" + " DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.VALUE1 IN "

+ " (SELECT MIN(DB2ADMIN.STOCK.VALUE1)"

+ " FROM DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.ID < "+rows

+ " GROUP BY DB2ADMIN.STOCK.INDEX1) "

+ " AND DB2ADMIN.STOCK.ID < "+rows;

stmt = con.prepareStatement( sql );

bFlag = stmt.execute();

rs1[0] = stmt.getResultSet();

}

}

StockListController.java:

package com.dimitri.controllers;

import java.io.IOException;

import java.util.HashMap;

import java.util.Map;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.ModelAndView;

import org.springframework.web.servlet.mvc.Controller;

import com.dimitri.beans.TestData;

import com.dimitri.servlets.Dispatcher;

import com.dimitri.spring.db.StockManager;

public class StockListController implements Controller{

private StockManager stockManager=null;

public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws

ServletException, IOException {

String rows=request.getParameter("rows");

String selectedOption=request.getParameter("selectedScreen");

83

if(selectedOption==null)selectedOption="0";

if(rows==null)rows="1000";

int n=Integer.parseInt(rows);

Map model=new HashMap();

long start=System.currentTimeMillis();

switch(Integer.parseInt(selectedOption))

{case Dispatcher.ALL_PRICES_SPRING:model.put("stocks", stockManager.getStocks(n));break;

case Dispatcher.CHEAPEST_PRICES_SPRING:model.put("stocks", stockManager.getCheapestStocks(n));break;}

long end=System.currentTimeMillis();

model.put("duration",new Long((end-start)).toString());

return new ModelAndView("stockListSpring", "model", model);}

public StockManager getStockManager() {return stockManager;}

public void setStockManager(StockManager stockManager) {this.stockManager = stockManager;}

}

StockManagerDaoJdbc.java:

package com.dimitri.spring.db;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.List;

import javax.sql.DataSource;

import org.springframework.jdbc.object.MappingSqlQuery;

import com.dimitri.beans.StockDetails;

public class StockManagerDaoJdbc implements StockManagerDao {

private DataSource ds;

public void setDataSource(DataSource ds) {this.ds = ds;}

public List getStockList(int n)

{StockQuery stockQuery=new StockQuery(ds,n);

return stockQuery.execute();}

public List getCheapestStockList(int n)

{CheapestStockQuery cheapestStockQuery=new CheapestStockQuery(ds,n);

return cheapestStockQuery.execute();}

class StockQuery extends MappingSqlQuery {

84

StockQuery(DataSource ds, int rows) {

super(ds, "SELECT INDEX,FULLNAME,VALUE,UP,DOWN,PREVIOUSCLOSE,DAYSLOWVALUE,"

+ " DAYSHIGHVALUE,TIMEOFRETRIEVAL FROM DB2ADMIN.STOCKS FETCH FIRST "+rows+" ROWS

ONLY");

compile();

}

protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {

StockDetails stockDetails = new StockDetails();

stockDetails.setIndex(rs.getString("INDEX"));

stockDetails.setFullName(rs.getString("FULLNAME"));

stockDetails.setValue(rs.getDouble("VALUE"));

stockDetails.setUp(rs.getDouble("UP"));

stockDetails.setDown(rs.getDouble("DOWN"));

stockDetails.setPreviousClose(rs.getDouble("PREVIOUSCLOSE"));

stockDetails.setDaysLowValue(rs.getDouble("DAYSLOWVALUE"));

stockDetails.setDaysHighValue(rs.getDouble("DAYSHIGHVALUE"));

stockDetails.setTimeOfRetrieval(rs.getDate("TIMEOFRETRIEVAL"));

return stockDetails;

}

}

class CheapestStockQuery extends MappingSqlQuery {

CheapestStockQuery(DataSource ds, int rows) {super(ds, "SELECT "

+ " DB2ADMIN.STOCK.INDEX1, DB2ADMIN.STOCK.FULLNAME, "

+ " DB2ADMIN.STOCK.VALUE1, DB2ADMIN.STOCK.UP1, "

+ " DB2ADMIN.STOCK.DOWN1, DB2ADMIN.STOCK.PREVIOUSCLOSE, "

+ " DB2ADMIN.STOCK.DAYSLOWVALUE, DB2ADMIN.STOCK.DAYSHIGHVALUE, "

+ " DB2ADMIN.STOCK.TIMEOFRETRIEVAL FROM DB2ADMIN.STOCK WHERE"

+ " DB2ADMIN.STOCK.VALUE1 IN (SELECT MIN(DB2ADMIN.STOCK.VALUE1)"

+ " FROM DB2ADMIN.STOCK WHERE DB2ADMIN.STOCK.ID < "+rows

+ " GROUP BY DB2ADMIN.STOCK.INDEX1) AND DB2ADMIN.STOCK.ID < "+rows);

compile();

}

85

protected Object mapRow(ResultSet rs, int rowNum) throws SQLException {

StockDetails stockDetails = new StockDetails();

stockDetails.setIndex(rs.getString("INDEX1"));

stockDetails.setFullName(rs.getString("FULLNAME"));

stockDetails.setValue(rs.getDouble("VALUE1"));

stockDetails.setUp(rs.getDouble("UP1"));

stockDetails.setDown(rs.getDouble("DOWN1"));

stockDetails.setPreviousClose(rs.getDouble("PREVIOUSCLOSE"));

stockDetails.setDaysLowValue(rs.getDouble("DAYSLOWVALUE"));

stockDetails.setDaysHighValue(rs.getDouble("DAYSHIGHVALUE"));

stockDetails.setTimeOfRetrieval(rs.getDate("TIMEOFRETRIEVAL"));

return stockDetails;}

}

}

StockManagerDao.java:

package com.dimitri.spring.db;

import java.util.List;

public interface StockManagerDao {

public List getStockList(int n);

public List getCheapestStockList(int n);

}

StockManager.java:

package com.dimitri.spring.db;

import java.util.List;

public class StockManager {

private StockManagerDao stockManagerDao=null;

public StockManagerDao getStockManagerDao()

{return stockManagerDao;}

public void setStockManagerDao(StockManagerDao stockManagerDao)

86

{this.stockManagerDao = stockManagerDao;}

public List getStocks(int n)

{return stockManagerDao.getStockList(n);}

public List getCheapestStocks(int n)

{return stockManagerDao.getCheapestStockList(n);}

}

stocks-servlet.xml:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">

<property name="mappings">

<props>

<prop key="/stockList.htm">stockListController</prop>

</props>

</property>

</bean>

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="viewClass"><value>org.springframework.web.servlet.view.JstlView</value></property>

<property name="suffix"><value>.jsp</value></property>

</bean>

<bean id="stockListController" class="com.dimitri.controllers.StockListController">

<property name="stockManager">

<ref bean="stockManager"/>

</property>

</bean>

<bean id="stockManager" class="com.dimitri.spring.db.StockManager">

<property name="stockManagerDao">

<ref bean="stockManagerDao"/>

</property>

</bean>

87

<bean id="stockManagerDao" class="com.dimitri.spring.db.StockManagerDaoJdbc">

<property name="dataSource">

<ref bean="dataSource"/>

</property>

</bean>

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">

<property name="jndiName" value="java:comp/env/mybank"/>

</bean>

</beans>

stockList.jsp:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<%@taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>

<HTML>

<HEAD>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

pageEncoding="ISO-8859-1"%>

<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

<META name="GENERATOR" content="IBM Software Development Platform">

<META http-equiv="Content-Style-Type" content="text/css">

<LINK href="theme/Master.css" rel="stylesheet" type="text/css">

<TITLE>Stock List Data page</TITLE>

</HEAD>

<BODY>

<jsp:useBean id="stocks" class="java.util.Vector" scope="request"></jsp:useBean>

<jsp:useBean id="duration" class="java.lang.String" scope="request"></jsp:useBean>

<H2 align="center">Real time Stock Information</H2>

<TABLE width="90%" border="1">

<TBODY>

<TR align="center">

88

<FORM method="post" action="">

<TD colspan="9">

<SELECT name="selectedScreen">

<OPTION value="0"<c:if test="${param.selectedScreen=='0'}"> selected</c:if>>All stock prices</OPTION>

<OPTION value="1"<c:if test="${param.selectedScreen=='1'}"> selected</c:if>>Cheapest prices only</OPTION>

<OPTION value="2"<c:if test="${param.selectedScreen=='2'}"> selected</c:if>>All prices (using SP)</OPTION>

<OPTION value="3"<c:if test="${param.selectedScreen=='3'}"> selected</c:if>>Cheapest prices (using

SP)</OPTION>

<OPTION value="4"<c:if test="${param.selectedScreen=='4'}"> selected</c:if>>All prices (using EJB)</OPTION>

<OPTION value="5"<c:if test="${param.selectedScreen=='5'}"> selected</c:if>>Cheapest prices (using

EJB)</OPTION>

<OPTION value="6"<c:if test="${param.selectedScreen=='6'}"> selected</c:if>>All prices (using Spring)</OPTION>

<OPTION value="7"<c:if test="${param.selectedScreen=='7'}"> selected</c:if>>Cheapest prices (using

Spring)</OPTION>

</SELECT>

<SELECT name="rows">

<c:forEach begin="100" end="20000" step="100" var="rows">

<OPTION value="${rows}"<c:if test="${param.rows==rows}"> selected</c:if>>Using ${rows} rows</OPTION>

</c:forEach>

</SELECT>

<INPUT type="submit" name="submit" value="Go">

</FORM>

Data Retrieved in ${duration} milliseconds

</TD>

</TR>

<TR>

<TD>Index</TD>

<TD>Name</TD>

<TD>Value</TD>

<TD>Up</TD>

<TD>Down</TD>

<TD>Prev. close</TD>

89

<TD>Low</TD>

<TD>High</TD>

<TD>Time</TD>

</TR>

<c:set var="counter" scope="request">0</c:set>

<c:forEach items="${stocks}" var="stock">

<c:set var="counter" scope="request" value="${counter+1}"></c:set>

<TR <c:if test="${counter%2==0}">bgcolor="silver"</c:if>>

<TD>${stock.index}</TD>

<TD>${stock.fullName}</TD>

<TD><fmt:formatNumber value="${stock.value}" type="currency"/></TD>

<TD><fmt:formatNumber value="${stock.up}" type="currency"/></TD>

<TD><fmt:formatNumber value="${stock.down}" type="currency"/></TD>

<TD><fmt:formatNumber value="${stock.previousClose}" type="currency"/></TD>

<TD><fmt:formatNumber value="${stock.daysLowValue}" type="currency"/></TD>

<TD><fmt:formatNumber value="${stock.daysHighValue}" type="currency"/></TD>

<TD><fmt:formatDate value="${stock.timeOfRetrieval}" type="date" dateStyle="full" /></TD>

</TR>

</c:forEach>

</TBODY>

</TABLE>

</BODY>

</HTML>

90

References

Books

[1] J. Weaver, K. Mukhar, and J. Crume, Beginning J2EE 1.4: From Novice to Professional, 2nd ed., Apress Beginner Series, 2004.

[2] K. Brown, G. Craig, G. Hester et, Enterprise Java Programming with IBM WebSphere, Second Edition, IBM Press, November, 2003.

[3] J. Hunter & W. Crawford, Java Servlet Programming, 2nd ed, O’Reilly

[4] Richard Monson-Haefel, Enterprise Javabeans, 3rd ed, O’Reilly

Internet Web Sites & Resources

[5] Sun Microsystems, “The J2EE 1.4 Tutorial,”http://java.sun.com/j2ee/1.4/docs/tutorial/doc/index.html (December 2005).

[6] Sun Microsystems, “The Java Tutorial”http://java.sun.com/docs/books/tutorial/

[7] The Apache Software Foundation, “Spring Framework 2.0 - Reference Manual,” http://static.springframework.org/spring/docs/2.0.x/reference/index.html, 2006.

[8] Best practices for tackling data bottlenecks within J2EE environments http://www.javaworld.com/javaworld/jw-04-2004/jw-0405-bottleneck.html

[9] WebSphere Application Server Version 6.0.x Information Center http://www-306.ibm.com/software/webservers/appserv/was/library/library60.html

[10] Rational Application Developer for WebSphere Software http://www-128.ibm.com/developerworks/rational/products/rad/

[11] DB2 Information Centerhttp://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?topic=/com.ibm.db2.udb.doc/core/c0008278.htm

[12] IBM HS20 Blade Server Product Informationhttp://www.blade.org/productdetail.cfm?RecordID=59

[13] Pace Virtual Private Network for Windows 2000/XP Usershttp://appserv.pace.edu/emplibrary/VPNWinXPRR121405.pdf

91

[14] Pace VPN client download URL (accessible from the university network only) http://compinfo.pace.edu/software.html