Persistence in EJB 3: A Practical Example

UNIVERSITY OF NOVI SAD
FACULTY OF SCIENCES
DEPARTMENT OF MATHEMATICS AND
COMPUTER SCIENCE
Persistence in EJB 3: A Practical Example
Term paper from course Software Engineering for Database Systems
Professor:
Dr. Miloš Racković
Student:
Robert Pap
Date:
May 19, 2011 Novi Sad
Table of Contents
ABSTRACT___________________________________________________________5
INTRODUCTION_______________________________________________________7
INTRODUCING EJB 3___________________________________________________9
A BRIEF HISTORY ABOUT EJB
OVERVIEW OF EJB 3
EJB 3 TYPES
THE HELLOUSER EXAMPLE
9
9
11
12
THE MODEL OF THE PRACTICAL EXAMPLE_______________________________13
THE IMPLEMENTATION OF THE PRACTICAL EXAMPLE_______________________17
THE PERSISTENCE AND DATABASE LAYER
THE BUSINESS LOGIC LAYER
THE PRESENTATION LAYER
17
29
39
THE PRACTICAL EXAMPLE IN ACTION___________________________________45
SETTING UP AND CONFIGURING PUBDB
STARTING PUBDB
45
47
CONCLUSION________________________________________________________55
3
Abstract
Enterprise software is software used in organizations, such as in a business or government
and is usually an integral part of an Information System. One of the major players in this field is
Enterprise JavaBeans (or EJB for short) developed by Sun Microsystems (now owned by Oracle).
This term paper was written to demonstrate some possibilities of EJB technology, namely the
persistence of data in databases. In EJB, this piece of technology is called Java Persistence API (or
JPA for short). JPA is a framework managing relational data in applications using the Java platform.
In order to achieve this functionality, JPA (and other similar technologies) use the Object-Relational
Mapping (or ORM for short) technique for converting data between incompatible type systems in
object-oriented programming languages (in this case, between Java and a relational database). With
this technique, a developer can manage data stored in a database as pure objects. Lastly, to query the
database, an ORM-friendly query language must be used: Java Persistence Query Language (or
JPQL for short). JPQL is an object-oriented query language and it is part of the JPA specification.
The technologies mentioned above will be demonstrated on a practical example. The goal of
this example was to develop an application called Publication Database (or pubDB for short),
which should store publications (articles and books) in a relational database and query the database
as needed using the technologies mentioned above.
This term paper is organized as follows: after a brief introduction, four chapters will be
presented. The first will briefly introduce the reader to the EJB 3 technology, and the remaining
three will focus on the example. The second chapter will introduce the ER-model of pubDB. The
third will show the inner details of the implementation itself, focusing on the persistence tier. The
fourth chapter will demonstrate the application in action, explaining how it is used, with a number
of screen-shots to better visualize the functionality of the application. The paper concludes with a
brief conclusion.
To develop the application, a number of other applications were used. For the development
environment, the free and open Eclipse IDE was used (version: Helios Service Release 1 v3.6.1)
<http://www.eclipse.org/>. As an application server, the free and open JBoss Application
Server v5.1.0.GA from Red Hat was used <http://www.jboss.org/jbossas>. For the relational
database server, the freely available MySQL Community Edition v5.5.10 from Oracle Corporation
was used <http://www.mysql.com/products/community/>. For the management and querying
of the MySQL server, the freely available MySQL Workbench v5.2.33b from Oracle Corporation
was used <http://www.mysql.com/products/workbench/>. Finally, for the creation of the
application's GUI, the free WindowBuilder Pro v0.9.0 from Google was used (as a plug-in of the
Eclipse IDE) <http://code.google.com/javadevtools/wbpro/>.
All diagrams in this paper were created using BOUML v4.23 patch 4, the freeware UML 2
modeling tool, and LibreOffice Draw v3.3.2, the free and open drawing tool. The homepages of
these
projects
can
be
found
at
<http://bouml.free.fr/>
and
<http://www.libreoffice.org/>, respectively.
5
Introduction
Enterprise software is software used in organizations, such as in a business or government,
contrary to software chosen by individuals. Enterprise software is usually an integral part of an
Information System. Some important characteristics of enterprise software are performance,
scalability and robustness [Wikipedia contributors 2011]. There are many competing technologies
which can be used to develop an enterprise software, like OMG's CORBA or Microsoft's .NET. One
of the major players in this field is also Enterprise JavaBeans (or EJB for short) developed by Sun
Microsystems (now owned by Oracle). EJB is a server-side component architecture technology for
the Java platform, enabling rapid and simplified development of applications based on Java
technology [Oracle 2011].
This term paper was written to demonstrate some possibilities of EJB technology, namely
the persistence of data in databases. In EJB, this piece of technology is called Java Persistence API
(or JPA for short). JPA is a framework managing relational data in applications using the Java
platform. In order to achieve this functionality, JPA (and other similar technologies) use the ObjectRelational Mapping (or ORM for short) technique for converting data between incompatible type
systems in object-oriented programming languages (in this case, between Java and a relational
database). With this technique, a developer can manage data stored in a database as pure objects.
Lastly, to query the database, an ORM-friendly query language must be used: Java Persistence
Query Language (or JPQL for short). JPQL is an object-oriented query language and it is part of the
JPA specification.
All these technologies will be explained in a little more detail in the next chapter, which is
devoted to EJB.
7
Introducing EJB 3
Generally speaking, Enterprise JavaBeans (or EJB for short) is a platform for building
portable, reusable and scalable business applications using Java. It's a component model or
framework that can be used to build enterprise Java applications without the need to reinvent some
commonly used services such as transactions, security, persistence, etc.
In this chapter, the reader can learn more about EJB 3 before moving on towards other
chapters. The chapter starts with a little background story about various releases of EJB, continues
with a general overview of the technology including EJB types and it concludes with a short
variation of the HelloWorld example. This chapter is based mostly on chapter 1 of [Panda, Rahman
& Lane 2007].
A Brief History About EJB
In contrast to popular claims, EJB was originally developed by IBM in 1997 and it was
clearly inspired by other technologies, mainly CORBA. Sun Microsystems adopted the EJB
specification a year later. EJB had three main reincarnations so far: EJB 1.0 was released in 1998
and EJB 1.1 in 1999. The second reincarnation started in 2001 with EJB 2.0 and continued with
EJB 2.1 in 2003. Finally, the third reincarnation came in 2006 with EJB 3.0. The most recent
version is EJB 3.1 released in 2009. [Wikipedia contributors 2011]
The story behind these reincarnations is rather interesting. The initial goal of EJB 1 was to
provide a simpler but innovative alternative to CORBA, but its functionality was rather limited.
However, by the time EJB 2 was released, it has become more functional but way too heavy and far
too complicated to be usable by developers. EJB's popularity was also decimated by Microsoft's
.NET and more importantly, by the lightweight and open-source tools like SpringSource's Spring
and JBoss' Hibernate. Clearly inspired by these lightweight projects, the goal of the new version of
EJB was to become lightweight but also to retain the functionality of the older versions. When EJB
3 was released, it literally shocked the IT world. However, the revolution had its price: its
specification was so different comparing to EJB 2 that it is considered that the two versions have
very little in common.1 With new characteristics such as POJO-programming, Java 5 annotations
(which replaced the verbose XML files), metadata programming, dependency injection, intelligent
defaulting, and finally, ORM-like persistence with JPA, the previous statement is surely true.
Overview of EJB 3
EJB is executed in a specialized runtime environment called the EJB container, which
provides a number of component services. At the other hand, persistence services are provided by a
specialized framework called the persistence provider.
EJBs are components, because they encapsulate their inner behavior, so the “clients” of the
components can use them without knowing anything about the inner details. The only thing they
must know is what to pass in and what to expect back. EJB components are lightweight compared
to heavyweight CORBA or COM+ components, because they are nothing more than a regular Java
class called POJO (abbr. Plain Old Java Object). Lastly, they are reusable either as an EJB or as a
1 If we would need to find comparisons to the different reincarnations of EJB, a ladybug, an elephant and a cow
would provide a rather good selection. EJB 1 could be referred as a ladybug, because of it's beauty but also because
of it's limited power. EJB 2 could be seen as an elephant: it's very powerful, but at the other hand, it's way too heavy
and hungry to be useful in all situations. Finally, EJB 3 could be referred as a cow: its functionality is first-rate, and
because of its moderate size, it's usable in most situations.
9
web-service.
EJB components live in a container. Together, these components and the container can be
seen as a framework that provides valuable services for enterprise application development, such
as managing application state, managing relational databases, managing transactions, implementing
security, etc. By default, EJB provides these common services out-of-the-box, so there's no need to
implement them from scratch. The container provides these services in a rather elegant way: with
metadata annotations. These are used to mark specific parts of a class (such as a field or method)
or the whole class with attributes. They were added with Java 5, and start with the @ character.
Because enterprise applications have a lot in common, it's no surprise that their architecture
is also similar. Developers can build an enterprise application by following some common
architecture principles. One of the most popular architecture is the traditional four-tier layered
architecture. As the name suggests, this architecture has four layers. The first is the presentation
layer which is responsible for rendering the graphical user interface (GUI for short) and managing
user input. This tier passes down each request to the second layer, the business logic layer. This tier
is the heart of the application and contains workflow and processing logic, such as actions and
processes of the application. It also manages some kind of a database (e.g. saving and retrieving
data). To achieve this, this layer utilizes the third tier, the persistence layer. This tier provides a
high-level object-oriented abstraction over the database tier. Finally, the database layer typically
consists of a relational database management system, such as Oracle, SQL Server, MySQL, etc. Of
course, EJB naturally supports this architecture. However, it's obvious that EJB is not a presentation
technology, so simple J2SE should be used for the first layer. Instead, EJB is all about robust
support for implementing the business logic and persistence layers.
Before moving on to the next section, it would be useful to properly emphasize some strong
points of EJB 3:
10
•
Ease of use – EJB 3 is one of the simplest server-side development platform around, and it's
learned fairly quickly. Features that emphasize this easiness are POJO-programming,
metadata annotations, sensible defaults and JPA. Most used services work out-of-the-box, so
the developer can focus on the application logic instead.
•
Integrated solution stack – EJB 3 offers a complete stack of server solutions, like
persistence, messaging, scheduling, remoting, web services, dependency injection (DI for
short), etc. Also, EJB 3 is seamlessly integrated with other Java technologies, such as JDBC,
Java Transaction API (JTA for short), Java Messaging Service (JMS for short), Java
Naming and Directory Interface (JNDI for short), Java Remote Method Invocation (RMI for
short). Also, Java presentation technologies such as Swing, JavaServer Pages (JSP for
short) and JavaServer Faces (JSF for short) are also supported. This renders the need for
other third-party technologies obsolete.
•
Open Java EE standard – EJB is part of the Java EE standard. Because of this, EJB has an
open and public specification, which organizations are encouraged to follow. Also, the EJB
3 standard was developed by the Java Community Process (JCP for short), which groups
individuals and organizations.
•
Broad vendor support – EJB is supported by a large number of organizations, such as IBM,
and open-source groups such as JBoss. This creates a competing market, which allows users
to choose from a wider selection of solutions.
•
Stable, high-quality code base – because the majority of the vendors are long-time
supporters and partners of Java and EJB, it guarantees that the quality of solutions will be
very high, and also ensures their stability.
EJB 3 Types
In EJB-speak, a component is called a “bean”. In EJB, there are three types of components:
•
Session Beans (SBs for short)
•
Message-Driven Beans (MDBs for short)
•
Entities
Naturally, each type has its purpose and can use only a specific subset of the EJB services.
SBs and MDBs live in the EJB container, which manages them and provides services to them. They
are used to build the business logic layer. In contrast, entities are used to model the persistence part
of the application. However, they aren't managed by the container, but by the persistence provider.2
A session bean is invoked by a client for the purpose of performing specific business
operations. The “session” name implies that a bean instance is available for the duration of a “unit
of work”. There are two types of SBs: stateful and stateless. A stateful SB automatically saves the
bean's state between client invocations. For example, the shopping cart of an E-business web-site
which allows users to shop (and the cart will remember all items put in him during the shopping
process), is a good example of a stateful SB. Another good example are wizards or questionnaires
on web-sites spreading several pages. In contrast, a stateless SB doesn't maintain any state, instead,
it models an application service that can be completed in a single client invocation. Checking the
balance of the user's credit card is such an invocation. SBs can be invoked either locally or remotely
via Java RMI. Also, a stateless SB can be exposed as a web-service. A developer can mark a POJO
as a SB with the annotations @Stateless and @Stateful (depending on the type of the SB).
Message-driven beans also process business logic, just like SBs, but with a big difference:
clients never invoke MDB methods directly. Instead, MDBs are triggered by messages sent to a
massaging server. An E-commerce web-site sending massages to a shipping company's application
to send the purchased articles from the warehouse to the buyer's address, would be a good example.
A developer can mark a POJO as a MDB with the @MessageDriven annotation.
Entities are de facto Java objects that are persisted into a database. Persistence is the ability
to have data contained in objects automatically stored in a relational database. Persistence in EJB 3
is managed by JPA by automatically persisting Java objects using a technique called ORM, which
maps data held in objects to database tables. Another way to explain entities is that they are the OO
representation of the database. Because entities are nothing more than POJOs, they fully support
the OO principles, like defining relationships between entities (tables), inheritance and
polymorphism. A POJO can be marked as an entity with the @Entity annotation.
In EJB 3, the persistence provider has three tasks: to manage the ORM configuration for
mapping, to provide CRUD (abbr. Create, Read, Update and Delete) and persistence operations to
entities (this is done by the EntityManager API), and to search and retrieve persisted data (this is
done by the Java Persistence Query Language or JPQL for short). While entities know how they
should be persisted, they aren't persisted by themselves. Instead, the EntityManager interface is
assigned to actually provide persistence services. It knows how to add, update, delete or retrieve
entities from the database. Additionally, JPQL is used to search for entities saved into the database.
JPQL was clearly inspired by SQL, because of their similar syntax, but JPQL works with entities,
not tables.3 Some JPQL queries will be presented in the third chapter.
2 It must be noted that EJB 2 had entity beans instead of the EJB 3 entities. That's because these entity beans were
also managed by the EJB container. However, this added unnecessary complexity to them.
3 It's worth noting that JPA also supports native SQL queries if needed.
11
The HelloUser Example
To demonstrate just how easy is to create a HelloWorld example in EJB 3, we'll include a
modified version of this example called HelloUser from [Panda, Rahman & Lane 2007].
package example;
@Local
public interface HelloUser {
public void sayHello(String name);
}
package example;
import java.ejb.Stateless;
@Stateless
public class HelloUserBean implements HelloUser {
public void sayHello(String name) {
System.out.println("Hello " + name + " welcome to EJB 3!");
}
}
It seems unbelievable (especially to those experienced in EJB 2) but this code is an example
of a perfectly valid EJB. As it can be seen, this is a stateless SB. A SB as a component must have at
least one business interface, that's why an interface is also included. This code shows just how
simplified EJB 3's programming model is. In fact, these classes are nothing more than regular
objects and interfaces called Plain Old Java Objects (POJOs for short) and Plain Old Java
Interfaces (POJIs for short). A POJO can be turned into a stateless SB with the @Stateless
annotation. In addition, the POJI is marked with the @Local annotation4. These metadata
annotations are part of the Java 5 specification and are heavily used in EJB 3. Earlier in EJB 2,
developers were forced to write verbose XML configuration files for each component. These files
were called as deployment descriptors. In EJB 3, the majority of the configuration can be done
using annotations, rendering these descriptors more or less obsolete5.
One last feature of EJB 3 which was not mentioned in more detail is dependency injection.
To access an EJB, EJB 2 used standard JNDI lookup, which was long and tedious. EJB 3 replaced it
with metadata-based dependency injection (DI for short)6. For example, the @EJB annotation can
“inject” EJBs transparently into an annotated variable, e.g. to access the HelloUser SB from
another EJB, the following code can be used:
...
@EJB
private HelloUser helloUser;
void hello() {
helloUser.sayHello("Robert");
}
...
This concludes the brief introduction of EJB 3. Starting from the next chapter, the main
focus will be on the practical example. First, the database model must be presented.
4 This means that the SB will be used locally by a client collocated in the same container (JVM) instance. If the client
is outside of the container instance, e.g. on a remote machine, the SB must be accessed remotely across the network
via Java RMI. In that case, the @Remote annotation must be used. Finally, if the client isn't even written in Java, the
SB can be exposed as a web-service with the @WebService annotation. A SB can have more than one interface.
5 However, a developer can continue to use deployment descriptors if that suits his/her needs better.
6 Of course, the developer can continue to use JNDI lookups, and in some situations, it's unavoidable.
12
The Model of the Practical Example
We will start the presentation of the practical example with the model of the information
system. Both the traditional Entity-Relationship model (ER-model for short) and object model will
be shown.
The Publication Database (or pubDB for short) is a simplified information system storing
and managing publications. As such, it's logical that the model of this system will have various
entities, like publications, authors, categories to which these publications can belong, etc.
The ER-model of pubDB consists of seven entity-types: Author, Category, Publication,
Article, Book, Journal and Publisher. An Author is uniquely identified by his or her identification
or ID number, but there are also other attributes, like the author's first name, last name and
biography. A Publication represents a generic publication. Each of them is uniquely identified by
its ID number, but there are other attributes, too: the title of the publication, the year it was
published and a description about the publication. An Author can write one or more Publications,
but it's not necessary (as such, an Author can exist without a single written Publication). At the other
hand, a Publication is written by at least one Author (a Publication cannot exist without a related
Author).
A generic Publication must either be an Article or a Book. A Publication which is an Article,
cannot be a Book, and a Book cannot be an Article. An Article, besides inheriting the common
attributes of a Publication, such as an ID number, title, year of publication and description (for
Articles, this should be the abstract), also has a unique attribute not available for Books: the Digital
Object Identifier (DOI for short). At the other hand, a Book, besides inheriting the common
attributes of a Publication (for Books, the publication description should be a review of the book),
has some other unique attributes not available for Articles: the number of pages, the International
Standard Book Number (ISBN for short) and a cover image.
An Article appears exclusively in a scientific Journal. An Article is published in exactly one
Journal. An Article cannot exist without a related Journal. A Journal can consist of zero, one or
more Articles. A Journal is uniquely identified by its ID number, but there are other references, like
its title and volume.7 At the other hand, a Book is published by a Publisher company. Similarly as
for Articles, a Book has exactly one Publisher. A Book cannot exist without a related Publisher. A
Publisher can publish zero, one or more Books. A Publisher is uniquely identified by its ID number,
but there are other references, too, like the name of the publisher and its address.
Lastly, all Publications should belong to a Category. A Publication must belong to exactly
one Category. At the other hand, a Category can be related to zero, one or more Publications. A
Category has only two attributes: its unique ID number and its label. However, the group of
categories should be considered as a hierarchy. As such, a particular Category can be a parent
category, a child category, none of them (if there is only one Category in the hierarchy), or even
both. A particular Category can have one or more subcategories (children), but it's not necessary for
the Category to have child categories at all. At the other hand, a Category can have zero or one
parent, however, it's not allowed for a Category to have two parents.
7 Note that two different volumes of a journal are considered as two different entities in the Journal entity-type, e.g.
the 24th and 25th number (volume) of the “IEEE Software” journal are two physically different journals.
13
LAST_NAME
FIRST_NAME
PUB_ID
PUB_TITLE
PUB_YEAR
subcategory
Author
(0, N)
Writes
(1, N)
Publication
(1, 1)
Belongs to
(0, N)
(0, N)
Category
Has
(1, 1)
CAT_ID
AUTHOR_ID
BIO
(0, 1)
PUB_DESC
CAT_LABEL
IS_A
PAGES
DOI
Article
(1, 1)
Book
ISBN
(1, 1)
COVER
Appears in
Published by
JOURNAL_ID
(0, N)
JOURNAL_TITLE
Journal
(0, N)
Publisher
JOURNAL_VOL
PUBLISHER_ID
PUBLISHER_NAME
PUB_ADDRESS
Image 1: The ER-model of pubDB
The ER-model of pubDB can be seen on Image 1. This model has some characteristics that
should be noted:
•
All relationships are bidirectional: an entity of an entity-type can always access it's related
entities belonging to other entity-types.
•
The majority of relationships are one-to-many, however, there is one many-to-many
relationship (between Author and Publication).
•
The relationship between Publication, Article and Book is modeled as a complete and
disjoint IS_A hierarchy. It's complete because each instance of the Publication superclass
belongs to at least one instance of a subclass (Article or Book). This means that the minimal
cardinality should be 1. At the other hand, this hierarchy is disjoint, because each instance of
the superclass can belong to at most one instance of a subclass. This sets the maximal
cardinality to 1. In our case, this means, that a Publication must either be an Article or Book
(there are no other possibilities), but a Publication cannot be both an Article and a Book at
the same time.
•
Most relationships are between two different entity-types, however, there is one particular
relationship, which models a relationship between only one entity-type: Category. Also
called a recursive relationship, it models a hierarchy of Categories. One particular category
can have zero, one or more subcategories, and zero or one parent category.
Based on this ER-model, an object model can be created. The object model of pubDB can be
seen on Image 2.
14
Author
idAuthor : string
lastName : string
firstName : string
0..1
Publication
idPub : int
writes
Category
belongs to
titlePub : string
1..*
*
biography : string
has
yearPub : int
*
idCat : int
1
labelCat : string
subcategory
*
desc : string
{complete,disjoint}
Article
Book
doi : string
pages : int
isbn : string
cover : byte[]
*
*
appears in
published by
1
1
Journal
Publisher
idJournal : int
idPublisher : int
titleJournal : string
namePublisher : string
volume : string
address : string
Image 2: The object model of pubDB
It would be tempting to see how this object model maps to the relational database. The result
of this mapping can be seen on Image 3. This is an EER-model (abbr. Enhanced ER-model) that
was automatically generated with MySQL Workbench. This diagram will be more interesting in the
next chapter, when we present the persistence layer.
This concludes the chapter describing the model of the practical example. In the next
chapter, the implementation of the Java application will be presented.
15
Image 3: The EER-model of pubDB
16
The Implementation of the Practical Example
The Publication Database (or pubDB for short) was developed utilizing the four-tier layered
architecture. This chapter will describe each tier by showing a class diagram for each of these
layers, following by a short description of all classes. In some cases, parts of the source code will
also be shown to demonstrate how these parts work. It must be emphasized that this term paper is
about persistence, so it's not a surprise that the persistence tier will have the main focus. Of course,
EJBs residing in the business logic layer should be also presented, but their priority will be only
secondary. Lastly, as this term paper is not about presentation, we won't spend much time on the
presentation tier.
The Persistence and Database Layer
As it was mentioned earlier, one task of the business logic layer is to manage some type of a
database. To achieve this, it must utilize the persistence layer, which provides a high-level objectoriented abstraction over the database tier. The database layer typically consists of a relational
database management system.
Image 4: Showing the SQL Editor in MySQL Workbench
PubDB uses MySQL as the database layer. Of course, this layer required no programming in
Java, however, it needed some configuration, e.g. creation of a user (with user-name and password),
creating a schema, and then assigning privileges to the newly created user to freely manage the new
17
schema. Every task involving the database was done in MySQL Workbench. However, this
application was useful during the whole development process, because it accepts SQL queries, so it
was also useful for testing (Image 4).
However, all other layers were created in Java with the Eclipse IDE. First, an Enterprise
Application (EAR for short) project was made with projectEAR as its name, and JBoss v5.0 as its
target runtime. An EAR is used to tie together one or more J2EE modules, including EJB modules.
When the EAR was created, it was time to create the persistence tier by creating a new JPA
project with the name projectJPA. For this JPA project, it was important to add it to the newly
created EAR.
The persistence tier has a single package called persistence. This package holds the EJB
entities and each entity is a POJO with the @Entity annotation. Image 5 shows the class diagram
for the persistence package. As it can be seen, the package has seven classes, which correspond
to the seven entities mentioned in the previous chapter: Author, Category, Publication, Article,
Book, Journal and Publisher. An observant reader will notice that the class diagram shown in Image
5 is practically the same as the object model shown in Image 2. This is not a coincidence, because
the object model shows a model in an OO way, and at the same time, EJB 3 entities model the
object model in the same way, although they are Java classes.
persistence
Category
Publication
Author
Article
Book
Journal
Publisher
Image 5: Class diagram of the persistence package
In the remainder of this section, some implementation details with source code fragments
will be given.
For example, let's see how the Author entity looks like. As the reader will notice, it's a
completely regular Java class (i.e. a POJO) named Author.java, with some annotations to attach
some additional functionality to the class.
18
package persistence;
import java.io.Serializable;
import java.lang.String;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
import static javax.persistence.FetchType.LAZY;
@Entity
// 1)
@Table(name="AUTHOR")
// 2)
@NamedQueries({ // 3)
@NamedQuery(name = "findAllAuthors",
query = "SELECT a FROM Author a"),
@NamedQuery(name = "findAuthorByName",
query = "SELECT DISTINCT a FROM Author a
WHERE a.lastName LIKE :authorName
OR a.firstName LIKE :authorName
OR CONCAT(a.firstName, a.lastName) LIKE :authorName
OR CONCAT(a.lastName, a.firstName) LIKE :authorName")}
)
public class Author implements Serializable {
@Id
@GeneratedValue
@Column(name = "AUTHOR_ID")
private int idAuthor;
// 4)
@Column(name = "LAST_NAME")
private String lastName;
@Column(name = "FIRST_NAME")
private String firstName;
@Lob
@Basic(fetch = LAZY)
@Column(name = "BIO")
private String biography; // 5)
@ManyToMany
@JoinTable(name = "AUTHOR_PUBLICATION",
joinColumns = @JoinColumn(name = "AP_AUTHOR_ID",
referencedColumnName = "AUTHOR_ID"),
inverseJoinColumns = @JoinColumn(name = "AP_PUB_ID",
referencedColumnName = "PUB_ID"))
private Set<Publication> publications;
// 6)
private static final long serialVersionUID = 1L;
public Author() {
super();
}
public int getIdAuthor() {
return this.idAuthor;
}
public void setIdAuthor(int idAuthor) {
this.idAuthor = idAuthor;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
19
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getBiography() {
return this.biography;
}
public void setBiography(String biography) {
this.biography = biography;
}
public Set<Publication> getPublications() {
return publications;
}
public void setPublications(Set<Publication> publications) {
this.publications = publications;
}
}
public void addPublication(Publication publication) {
if(publications == null) {
publications = new HashSet<Publication>();
}
publications.add(publication);
}
// 7)
Some lines in the source code were marked with a number inside a commentary. Let's
explain them:
1) As it was explained earlier, the @Entity annotation marks this POJO as an EJB entity. This
is all that the developer needs to do to transform a POJO into an entity.
2) The @Table annotation specifies the table containing the columns to which the entity is
mapped. The name parameter sets the name of the table in the relational database (in this
case, it will be set to AUTHOR).8
3) The @NamedQuery annotation specifies a JPQL query. There are two types of a query:
dynamic and named queries. Dynamic queries are created on the fly during runtime, so
they are defined in class methods. Named queries, on the other hand, are defined in entities
using the @NamedQuery annotation (and grouped with the @NamedQueries annotation if
there are more of them). Named queries are used when a particular query is used many times
in an application and it's always fixed. These queries are created before runtime, so they are
faster than dynamic queries. The @NamedQuery annotation has two parameters. The name
parameter sets the name of the query. Later, when the query will be needed, it's enough to
call the query by its name. The query parameter contains the query itself. As it can be seen,
JPQL is almost the same as native SQL, but it works with entities, not tables (additionally,
the results of a JPQL query will be also entities). One more interesting part of a query is the
expression beginning with the colon sign, e.g. :authorName. This is a parameter, which will
be passed to the query by the method calling the query. We'll continue the discussion about
queries later when we show the session beans.
4) After defining this POJO as an entity, it's time to define the fields of the class, which will
8 Note that the @Table annotation is entirely optional. If left out, the table will be named as the entity itself: Author.
20
correspond as table columns in the database. Let's show the basic principles with the
idAuthor field. This field has three annotations. The @Column annotation maps a persisted
field or property to a table column. It has many parameters 9, but the only one which is
actually used here is the name parameter, which sets the name of the table column in the
database (in this case, to AUTHOR_ID).10 This field also has an @Id annotation. This tells the
database that this field will serve as a primary key to this table. Lastly, the
@GeneratedValue annotation sets this primary key as synthetic11.
5) The biography field has some advanced annotations. The @Lob annotation (abbr. Large
OBject) tells the database that this field will hold “heavy” objects, such as long textual
strings, or even non-character type files like pictures. 12 Because these items can be very big,
it's recommended to optimize their retrieval by setting the fetch strategy in the @Basic
annotation to FetchType.LAZY13.
6) Until now, we haven't discussed about relationships between entities (and tables). It's well
known that there are three types of relationships: one-to-one, one-to-many and many-tomany. Here, the many-to-many relationship will be discussed. This is achieved with the
@ManyToMany annotation. As it was defined earlier, there is a many-to-many relationship
between an Author and Publication. This means that an Author can be connected to many
Publications and vice versa. This is represented by the publications field, which type is
Set<Publication> (i.e. a set of publications). Because many-to-many relationships don't
exist in a relational database, the tables representing these entities must be joined with the
help of a third table which will hold the IDs of both tables (e.g. an author with the ID 5 is
connected to publications 21 and 44, etc.). In EJB 3, this is achieved with the @JoinTable
annotation, which has three parameters. The name parameter defines the name of the third
table. The joinColumns parameter defines, which field from this entity will be copied to the
third table (in this case, the Author's identification field). The inverseJoinColumns
parameter defines the same thing, but from the other table (in this case, the Publication
entity, and its ID field). The last two parameters practically define the foreign keys of the
third table (which will reference to the other two tables) and are defined with the
@JoinColumn annotation with two parameters: the name parameter sets the name of the
column in the third table, and the referencedColumnName parameter tells which column
from the original table corresponds to this column in the third table. Note that there's no
need to define the same thing on the other side of the relationship (i.e. in the Publication
entity), but more about that later.
7) Finally, the addPublication(…) method is used to add publications to an author. This is a
standard method for adding objects to collections, and it's widely used in one-to-many and
many-to-many relationship type entities.
9 For example, whether the field is unique, nullable, insertable, updatable, etc.
10 Similar to the @Table annotation, the @Column annotation can be omitted, too. In that case, the name of the table
column will be set as the name of the field itself: idAuthor.
11 There are two primary key types: natural and synthetic. Natural keys model real-world identification properties. For
example, a person can be uniquely identified by an identity card number, a book can be identified by its ISBN, etc.
However, these identifications are not safe, because they can be changed for some reason. Because of this, synthetic
keys are better. They are automatically generated by the database itself with a number generator.
12 Long textual strings are called CLOBs (abbr. Character Large OBjects), and non-character files are called BLOBs
(Binary Large OBjects). The @Lob annotation unites both LOB types, depending on the field type. CLOBs are
represented as a String, and BLOBs as byte[]. As it can be seen, the biography field is a CLOB.
13 A lazy fetch will postpone the retrieval of this field for as long as possible. For example, when a user needs only the
author's name, the persistence provider won't really retrieve the author's biography, only the author's name.
21
To summarize, the Author entity is a simple POJO: it has a standard default constructor,
some private fields, their getters/setters and some annotations. This is how an entity is created in
EJB 3. It's not necessary to write annotations to the fields. EJB 3 has a feature called automatic
table generation, however, it's not recommended to use it. It's always better to manually set the
fields, but as it can be seen, it's fairly easy. One last thing to mention is that it's not necessary to add
these annotations to fields, because they can be added to the getters/setters instead. Adding them to
the fields is called field access, and adding them to getters/setters is called property access. It's up
to the developer to choose, which style will be used, but it must be noted, that inside an entity, only
one access type can be used.
Let's see how the Publication.java class is implemented next.
package persistence;
import
import
import
import
javax.persistence.*;
static javax.persistence.DiscriminatorType.STRING;
static javax.persistence.FetchType.LAZY;
static javax.persistence.CascadeType.MERGE;
import …
// some other standard imports
@Entity
@Table(name="PUBLICATION")
@Inheritance(strategy=InheritanceType.JOINED)
// 1)
@DiscriminatorColumn(name = "PUB_TYPE", discriminatorType = STRING, length = 1)
@NamedQueries({
// 2)
@NamedQuery(name = "findPublicationByTitle",
query = "SELECT p FROM Publication p WHERE p.titlePub LIKE :pubTitle"),
@NamedQuery(name = "findPublicationByYear",
query = "SELECT p FROM Publication p
WHERE p.yearPub BETWEEN :yearStart AND :yearEnd"),
@NamedQuery(name = "findPublicationByAuthor",
query = "SELECT DISTINCT p FROM Publication p INNER JOIN p.authors a
WHERE a.lastName LIKE :authorName
OR a.firstName LIKE :authorName
OR CONCAT(a.firstName, a.lastName) LIKE :authorName
OR CONCAT(a.lastName, a.firstName) LIKE :authorName"),
@NamedQuery(name = "findPublicationByCategory",
query = "SELECT p FROM Publication p INNER JOIN p.category c
WHERE c.labelCat LIKE :categoryLabel")}
)
public abstract class Publication implements Serializable {
// 3)
@Id
@GeneratedValue
@Column(name = "PUB_ID")
private int idPub;
@Column(name = "PUB_TITLE")
private String titlePub;
@Column(name = "PUB_YEAR")
private int yearPub;
@Column(name = "PUB_DESC")
@Lob
@Basic(fetch = LAZY)
private String desc;
@ManyToMany(mappedBy = "publications", cascade = MERGE)
private Set<Author> authors;
// 4)
@ManyToOne(cascade = MERGE)
@JoinColumn(name = "PUBLICATION_CAT_ID", referencedColumnName = "CAT_ID")
private Category category;
// 5)
22
private static final long serialVersionUID = 1L;
public Publication() {
super();
}
// getters/setters for the fields: idPub, titlePub, yearPub and desc
public Set<Author> getAuthors() {
return authors;
}
public void setAuthors(Set<Author> authors) {
this.authors = authors;
}
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
public void addAuthor(Author author) { // analogue to the addPublication(…)
if(authors == null) {
// method in Author.java
authors = new HashSet<Author>();
}
authors.add(author);
}
Similar to the previous class, some parts of the source code are marked with a number inside
a commentary. Let's explain them:
1) Some readers will remember from the chapter where the model of pubDB was explained that
the Publication entity forms an IS_A hierarchy relationship with the Article and Book
entities. This is, in fact, an inheritance relationship. In EJB 3, to mark an entity as a
superclass in the inheritance relationship, the @Inheritance annotation is used. Its
strategy parameter defines which inheritance strategy will be used. There are three
strategies: single-table (the default), joined-tables and table-per-class strategy.14 To set the
strategy,
set
the
parameter
to
InheritanceType.SINGLE_TABLE,
14 In the single-table strategy, all classes in the inheritance hierarchy are mapped to a single table, which must contain
all data from all entities. This means that this table will contain columns that are common to all entities (the fields of
the superclass), and columns that are specific to a particular entity (the fields of the subclasses). An additional
column tells to which entity a particular table row belongs. This column is called a discriminator column. This
strategy is fast and simple, but bloated, because the table will have many null values in the specific columns (e.g. the
Book entity has a specific ISBN field, but since an Article cannot have an ISBN, the ISBN field will be empty for all
articles).
In the joined-tables strategy, one-to-one relationships are used to model OO inheritance. It creates separate tables
for each entity in the OO hierarchy and relates direct descendants in the hierarchy with one-to-one relationships. In
this case, the parent table contains only the common columns and a discriminator column. The child tables contain
the columns that are specific to that particular child. This solves the column duplication problem of the previous
strategy. Also, the joined-tables strategy is probably the best from a design perspective, however it's somewhat
slower than the single-table strategy.
Lastly, the table-per-class strategy creates tables for each entity in the hierarchy, but without any connection
between them. Because of this, the tables must also contain some columns even if they are common, which results in
column duplication (the specific columns, however, are not duplicated). This is the only strategy which doesn't use
a discriminator column. This strategy is easy to understand, but it doesn't use any relational or OO features. Because
of this, manipulating and querying data from the database is hard. Developers are advised to avoid this strategy
when possible.
23
or InheritanceType.TABLE_PER_CLASS. As it can be seen, in
this case, the joined-tables strategy was used. The discriminator column can be defined with
the @DiscriminatorColumn annotation. This annotation has three parameters: the name
parameter defines the name of the discriminator column in the parent table, the
discriminatorType parameter defines which value type will be used for the discriminator
column (in this case, it will be a string), and the length parameter tells the length of the
string (in this case, one-letter strings will be used). The discussion about inheritance will
continue later when we present the subclasses.
InheritanceType.JOINED
2) As it can be seen, this entity has four named queries. Most of them are fairly common, but
the last two are a little more advanced. Namely, they join two tables with the INNER JOIN
command. Similar to SQL, this joins two entities. For example, the query with the name
findPublicationByCategory searches for all publications which belong to a particular
category.
3) Observant readers surely noticed that this class is set to abstract. The reason should be
obvious. Because this IS_A hierarchy is complete, each publication is either an article or a
book. This means, that a publication cannot exist on its own. With the Publication class set
to abstract, it prohibits any creation of a Publication object instance.
4) As we remember, the Author and Publication entities are connected with a many-to-many
relationship. Both sides of the relationship should be marked, that's why the authors field is
marked with the @ManyToMany annotation. However, as it was mentioned, there's no need to
specify the details of the relationship on both sides. As the details were specified in
Author.java, there's no need to specify them again. However, it must tell the persistence
provider where is the other end of the relationship. This is achieved with the mappedBy
parameter. It practically defines the “owning“ side of the relationship. In this case, it tells
that the owner of this relationship is the publications field in the Author entity. 15 The
cascade parameter is also interesting. In databases, cascade operations are often used.16
EJB 3 also allows this feature. A developer can enable the cascade property for a specific
EJB 3 persistence operation, or for all of them. As such, the cascade parameter can be set to
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REFRESH,
CascadeType.REMOVE or CascadeType.ALL. We will see some of these operations such as
persist, merge or remove in the next section.
5) According to the model of pubDB, a Publication is not only related to an Author but also to
a Category. While the relationship between Author and Publication is many-to-many, the
relationship between Category and Publication is one-to-many. In EJB 3, this is represented
with the @OneToMany and @ManyToOne annotations, depending on the side of the
relationship. The category field is marked with the @ManyToOne annotation, because a
publication can only belong to a single category. The specifics of the relationship are defined
in the @JoinColumn annotation, similar to the many-to-many relationship. It tells that a
column specified by the name parameter will be a foreign key which will refer to the primary
key of the other entity specified by the referencedColumnName parameter. The other side of
this relationship will be shown a little later.
15 Note that this relationship “owner” concept doesn't originate from domain modeling, but it's a convenient way to
ease the specification of the relationship by defining the details only on the owning side. For many-to-many
relationships, it's not important which side will be the owner.
16 Cascading means that an operation on an entity is also propagated to related entities. The most illustrative way to
explain this is with the delete operation. Suppose that we want to delete an author and all related publications. By
simply deleting the author entity, the related publications won't be deleted. However, by cascade deleting the author,
all related publications will be also deleted. The same principle applies during cascade persisting or cascade
updating.
24
There are two subclasses of the Publication entity: Article and Book. It's enough to present
only one of them, because they are rather similar. As Book.java is a little more interesting than
Article.java, we will skip the presentation of the latter and will focus on the former instead.
package persistence;
import
import
import
import
javax.persistence.*;
persistence.Publication;
static javax.persistence.FetchType.LAZY;
static javax.persistence.CascadeType.MERGE;
import …
// some other standard imports
@Entity
@Table(name="BOOK")
@DiscriminatorValue("B")
// 1)
@PrimaryKeyJoinColumn(name = "PUB_ID", referencedColumnName = "PUB_ID")
@NamedQueries({
@NamedQuery(name = "findBookByISBN",
query = "SELECT b FROM Book b WHERE b.isbn LIKE :isbn"),
@NamedQuery(name = "findBookByPublisher",
query = "SELECT DISTINCT b FROM Book b INNER JOIN b.publisher p
WHERE p.namePublisher LIKE :publisherData
OR p.address LIKE :publisherData
OR CONCAT(p.namePublisher, p.address) LIKE :publisherData
OR CONCAT(p.address, p.namePublisher) LIKE :publisherData")}
)
public class Book extends Publication implements Serializable {
// 2)
@Column(name = "PAGES")
private int pages;
@Column(name = "ISBN")
private String isbn;
@Column(name = "COVER")
@Lob
@Basic(fetch = LAZY)
private byte[] cover;
// 3)
@ManyToOne(cascade = MERGE)
@JoinColumn(name = "BOOK_PUBLISHER_ID",
referencedColumnName = "PUBLISHER_ID")
private Publisher publisher;
// 4)
private static final long serialVersionUID = 1L;
public Book() {
super();
}
// getters/setters for the fields: pages, isbn, cover and publisher
}
As usual, some parts of this source code are marked with a number inside a commentary.
These will be explain next:
1) As it is known, the Book entity forms an IS_A hierarchy with Publication and Article, with
Publication as the superclass. This is implemented with a simple inheritance. In a relational
database, this type of relationship can be modeled with three different strategies, and as it is
known, in this case, the joined-tables strategy was chosen. Some details were defined in
25
Publication.java,
however, there is some work that needs to be done in the subclasses as
well. First, a discriminator column value must be chosen for the subclasses. In
Publication.java, this was defined as a one-letter string. As such, choosing the letter “B”
for Books and “A” for Articles would be a logical choice. This is exactly what the
@DiscriminatorValue annotation does. However, there is one more thing to be done: a
foreign key should also be defined. In the joined-tables strategy, this is done with the
@PrimarayKeyJoinColumn annotation. It's very similar to the @JoinColumn annotation.
While @JoinColumn between two entities defines a foreign key where both entities will have
a primary key and one entity will hold an additional foreign key (which references to the
other entity's primary key), @PrimaryKeyJoinColumn defines a foreign key where only one
entity will have a primary key, and the other will have only a foreign key referencing the
first entity's primary key (and hence, this foreign key becomes this entity's primary key).
The usage of this annotation is practically the same as the other annotation's. In this case, the
Book entity will have a PUB_ID foreign key which references the primary key of the
Publication entity (also called PUB_ID).
2) Because the Book entity is a subclass of Publication, it must extend the Publication object.
However, this class is not abstract as the superclass. This means, that it's allowed to create
instances of the Book object. The same applies to the Article entity.
3) The Book class also has a LOB field: cover. However, observant readers will notice that this
field is not like the previous ones, which were CLOBs. The cover field is actually a BLOB
(because of its type, which is byte[]), and it should hold a picture of the book's cover. From
a developer's perspective, there's no difference between this BLOB and the previous CLOBs.
4) The Book entity is exclusively related to the Publisher entity (besides the Author and
Category entity inherited from Publication). In this case, under exclusively we mean that the
Article entity cannot be related to Publisher, however, the Article entity is exclusively
related to the Journal entity instead. There is a one-to-many relationship between Publisher
and Book. The annotations belonging to the publisher field clearly show this. Also note,
that the relationship between the Journal and Article entities is also one-to-many.
It would be tempting to present the Publisher and Journal entities, however, both entities are
fairly standard and all their features were seen already in previous entities. The Category entity, on
the other hand, should be very interesting. We will present it next.
package persistence;
import javax.persistence.*;
import …
// some other standard imports
@Entity
@Table(name="CATEGORY")
@NamedQueries({
@NamedQuery(
name = "findCategoryByLabel",
query = "SELECT DISTINCT c FROM Category c
WHERE c.labelCat LIKE :categoryLabel"),
@NamedQuery(
name = "findAllRootCategories",
query = "SELECT c FROM Category c WHERE c.parent IS NULL")}
)
26
public class Category implements Serializable {
@Id
@GeneratedValue
@Column(name = "CAT_ID")
private int idCat;
@Column(name = "CAT_LABEL")
private String labelCat;
@OneToMany(mappedBy = "category")
private Set<Publication> publications;
// 1)
@ManyToOne
@JoinColumn(name = "PARENT_CAT_ID", referencedColumnName = "CAT_ID")
private Category parent;
// 2)
@OneToMany(mappedBy = "parent")
private Set<Category> subCategories;
private static final long serialVersionUID = 1L;
public Category() {
super();
}
//
getters/setters for the fields: idCat and labelCat
public Set<Publication> getPublications() {
return publications;
}
public void setPublications(Set<Publication> publications) {
this.publications = publications;
}
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
public Set<Category> getSubCategories() {
return subCategories;
}
public void setSubCategories(Set<Category> subCategories) {
this.subCategories = subCategories;
}
public void addSubCategory(Category category) {
// analogue to the addPublication(…) method in Author.java
}
}
public void addPublication(Publication publication) {
// analogue to the addPublication(…) method in Author.java
}
As usual, here are the explanations of the numbered parts in the source code shown inside a
commentary:
1) First, we remember that there is a one-to-many relationship between the Category and
Publication entities. Earlier in the Publication entity, the category field was marked with
the @ManyToOne annotation. This, however, means, that this end of the relationship will be
marked with the @OneToMany annotation, because one category can be related to more
publications. The only parameter that can be seen here is the mappedBy parameter. Similar to
27
many-to-many relationships, it tells that the “owning” side of the relationship is the
category field in the Publication entity.17
2) The Category entity also sports an interesting feature: this entity is in a relationship with
itself, more precisely, it has a recursive relationship. This means, that a particular category
can have a parent category, and it can also have child categories (or subcategories).
However, in reality, this relationship is nothing more than a one-to-many relationship, with
both ends residing in the same entity. This is clearly shown in the implementation. The
parent field is of type Category, and it's marked with the @ManyToOne annotation, meaning
that a particular category can have at most one parent. As usual, the @JoinColumn
annotation defines the foreign key named as PARENT_CAT_ID which will reference CAT_ID.
At the other hand, the subcategories field (which represents the other end of the
relationship) is marked with the @OneToMany annotation with the usual mappedBy parameter.
It must be also noted, that both fields ( parent and subcategories) should have
getters/setters.
Based on these mapping properties, EJB and JBoss AS will create the appropriate tables in
the MySQL relational database. The result of this mapping was shown in the previous chapter, on
Image 3. This diagram shows that the object and relational representation of the model is not the
same (one obvious difference would be the relational representation of the many-to-many
relationship), but EJB 3 manages all these differences on the fly, behind the scenes.
This concludes the presentation of the persistence layer. In the next section, the business
logic layer will be described. This is the layer where session beans and message-driven beans live.
17 Unlike in many-to-many relationships, where it's not important which side of the relationship will be the “owner”, in
one-to-many relationships, the relationship owner is always the field which is marked with the @ManyToOne
annotation. This means, that the mappedBy parameter will always reside inside the @OneToMany annotation. In fact,
specifying mappedBy inside the @ManyToOne annotation will result in a fatal error.
28
The Business Logic Layer
The business logic layer is located between the presentation and persistence tier. It's
practically the heart of the application and contains workflow and processing logic, such as actions
and processes of the application. At one hand, the presentation layer passes down user requests and
data to it. Here, these requests are processed. These processes usually involve database management
(such as CRUD (abbr. Create, Read, Update and Delete) and search operations). To achieve this, the
business logic layer must communicate with the third tier, the persistence layer. The persistence tier
persists or retrieves persisted data, passes it back to the business logic layer, which (after
manipulating this data as necessary) passes it back to the presentation tier. It must be noted,
however, that this is only a general idea about the usage of this layer.
The business logic layer is where session and message-driven beans live. In Eclipse, to
create the business logic layer, a new EJB project must be created. In the case of pubDB, its name
was set as projectEJB, the EJB module version was set to version 3.0, and it was added to the
previously created EAR (named projectEAR), just like in the persistence tier earlier. The client JAR
(abbr. Java ARchive) creation was disabled.
Image 6: The class diagram of the buslogic package
The business logic tier has only one package called buslogic. The classes of this package
are simple POJOs and POJIs. Because the main focus of pubDB was to demonstrate the persistence
tier, only the most basic EJBs were used in the business logic layer, that is, only stateless session
beans. Unlike EJB entities, SBs consist of two parts: at least one business interface, and the
29
implementation of this interface (or interfaces). The class diagram of the buslogic package can be
seen on Image 6. This package has several SBs, one for each entity. Each SB groups together all
operations involving a particular entity (e.g. the AuthorManager SB groups together all operations
involving authors)18. As it can be seen, each SB has two business interfaces: a local and a remote.
However, as it will be shown, the local interface is actually never used, and it was included only for
the sake of completeness.19
In the remainder of this section, some implementation details with source code fragments
will be given.
For example, let's see some implementation details of the AuthorManager SB. First, the
remote business interface will be shown, which is called AuthorManagerRemote.java.
package buslogic;
import java.util.List;
import javax.ejb.Remote;
@Remote
// 1)
public interface AuthorManagerRemote {
void addAuthor(String lastName, String firstName, String biography); // 2)
void updateAuthor(int idAuthor, String lastName, String firstName,
String biography);
// 3)
void deleteAuthor(int idAuthor);
// 4)
List<Object[]> populateAuthorList();
// 5)
List<String> retrieveAuthor(int idAuthor);
// 6)
List<Object[]> findAuthor(String authorName);
// 7)
}
As it can be seen, the business interface is pretty straightforward. It lists the methods (or
services) of this SB, according to the principles of components. The numbered parts inside a
commentary will be explained next:
1) To mark this POJI as a remote interface of a SB, the @Remote annotation is used.20
2) The addAuthor(…) method, as its name suggests, adds an author to the database.
3) The updateAuthor(…) method updates an already persisted author in the database. The
author is identified by his or her ID number.
4) The deleteAuthor(…) method deletes an author from the database.
5) The populateAuthorList() method is used strictly by a GUI component (more precisely, a
JList component). It finds all authors in the database, returns the results back to the GUI,
which populates the JList with data from the results.
6) The retrieveAuthor(…) method finds a particular author by his or her ID number.
7) Finally, the findAuthor(…) method searches the database for authors by his or her name.
18 With the exception of PublicationManager which groups together the operations involving the subclasses of
Publication: Article and Book.
19 It can be asked, why was only the remote interface used. Although all layers of pubDB were developed on a single
developer machine, which should imply that the usage of the local interface should be enough, the remote interface
was used instead to demonstrate the use of JNDI.
20 The AuthorManagerLocal POJI is exactly the same as AuthorManagerRemote, the only difference is that it's
marked with the @Local annotation instead. To transform a POJI into a web-service, the @WebService annotation
should be used.
30
Let's
show
now the implementation of this business interface. It's called
AuthorManager.java and implements both the AuthorManagerRemote and AuthorManagerLocal
interfaces.
package buslogic;
import
import
import
import
import
import
import
java.util.ArrayList;
java.util.Iterator;
java.util.List;
javax.ejb.Stateless;
javax.persistence.EntityManager;
javax.persistence.PersistenceContext;
javax.persistence.Query;
import
import
import
import
persistence.Article;
persistence.Author;
persistence.Book;
persistence.Publication;
@Stateless(mappedName = "authorManager")
// 1)
public class AuthorManager implements AuthorManagerRemote, AuthorManagerLocal {
@PersistenceContext
protected EntityManager em;
// 2)
public AuthorManager() {
// default constructor
}
public void addAuthor(String lastName, String firstName,
String biography) {
Author author = new Author();
author.setLastName(lastName);
author.setFirstName(firstName);
author.setBiography(biography);
em.persist(author);
}
// 3)
public void updateAuthor(int idAuthor, String lastName, String firstName,
String biography) {
// 4)
Author author = em.find(Author.class, idAuthor);
author.setLastName(lastName);
author.setFirstName(firstName);
author.setBiography(biography);
em.merge(author);
}
public void deleteAuthor(int idAuthor) {
// 5)
Author author = em.find(Author.class, idAuthor);
em.remove(author);
}
public List<Object[]> populateAuthorList() {
// this method isn't a general CRUD operation, but a specific one needed
// by a JList GUI component, and as such, its details will be omitted
}
31
public List<String> retrieveAuthor(int idAuthor) {
Author author = em.find(Author.class, idAuthor);
List<String> authorData = new ArrayList<String>();
// now fill the List with data from the retrieved author and return it
}
public List<Object[]> findAuthor(String authorName) {
Query query = em.createNamedQuery("findAuthorByName");
query.setParameter("authorName", authorName);
List list = query.getResultList();
List<Object[]> authorList = new ArrayList<Object[]>();
// 6)
Iterator<Author> it = list.iterator();
Author author;
while(it.hasNext()) {
author = it.next();
// populate authorList with the author's data
// finally, get the publications that are related to this author
for(Publication publication : author.getPublications()) {
if(publication instanceof Article) {
// this Publication is an Article
}
else if(publication instanceof Book) {
// this Publication is a Book
}
}
// final preparation of authorList
}
}
return authorList;
}
Let's explain the numbered parts inside a commentary to better understand how CRUD
operations were in EJB 3:
1) To mark a POJO as a stateless SB, the @Stateless annotation is used. The mappedName
parameter is used by vendors to assign the given string as a global JNDI name for the EJB.
The client will find this EJB by looking up the authorManager name.
2) In an EJB, the first step to performing any persistence operations is obtaining an instance of
an EntityManager. This can be done by injecting the instance using the
@PersistenceContext annotation.21
3) A standard create CRUD operation can be seen in the addAuthor(…) method. It creates a
new instance of the Author entity and fills it with data (using the public setter methods
defined in Author.java). At the end, it persists (adds) this Author instance to the database
by calling the persist(…) method of the EntityManager's em instance.
4) The updateAuthor(…) method shows a standard update CRUD operation. Of course, first it
needs to find the author by his or her ID number. In EJB 3, there's no need to write a
standard query for this basic purpose. Instead, the find(…) method of the EntityManager
should be used. This method has two parameters: the first specifies the type of the
21 Typically this is all a developer needs to do to get an instance of EntityManager. All EntityManager instances
injected with the @PersistenceContext annotation are managed by the EJB container. This means that the
container takes care of the cumbersome task of looking up, opening and closing the EntityManager behind the
scenes. Of course, a developer can choose to manage it manually. In that case, the EntityManager is not
container-managed, but application-managed.
32
retrievable entity (in this case, Author.class), and the second specifies the entity's primary
key. The EntityManager searches for the author by the given primary key (his or her ID),
and returns it as an entity instance of type Author. After this, the data of the retrieved author
is changed. However, at the end, instead of calling the persist(…) operation, the merge(…)
operation is called. This only updates the already existing entity.
5) The deleteAuthor(…) method shows a pretty straightforward delete CRUD operation. First
it must find the deletable author, and then it removes the entity from the database with the
remove(…) operation of EntityManager.
6) The findAuthor(…) method which searches the database for authors by their name, is a
little more complicated. This is the first method that uses queries. As it can be seen, it uses
the findAuthorByName named query. Using a named query is pretty simple: call the
createNamedQuery(…) operation from EntityManager and pass the name of the named
query as its parameter. The EntityManager will prepare the query and store it as a Query
object.22 The next step is to set the query parameters. As we recall, this query is defined in
Author.java and looks like this: SELECT DISTINCT a FROM Author a WHERE
a.lastName LIKE :authorName OR a.firstName LIKE :authorName OR (…). In this
query, :authorName is a query parameter.23 This parameter must be replaced by a real value
before executing the query. This is achieved with the setParameter(…) operation of Query,
which has two parameters: the first identifies the query parameter, and the second sets it
with a real value. Note that if a particular query has two or more different query parameters,
the developer needs to call the setParameter(…) method as many times as there are
different parameters. The final step should be to execute the query by calling the
getResultList() method of Query. The result will be stored in a List (if no results were
found, the list will be empty). The rest of the findAuthor(…) method is not really important
for this demonstration. Basically, the method needs to return a list populated with data from
the result list. To walk through the list which was returned by getResultList(), an
Iterator should be used, or a “for each” loop. One more interesting detail of this method
is the effective use of OO polymorphism. As it is known, the Author entity is in a
relationship with the Publication entity, however, a publication should either be an Article or
Book. Publications that are related to a particular author can be retrieved through Author's
publications
field
(more
precisely,
through
its
getter
method
author.getPublications()). The returned entities are of type Publication, but because
EJB 3 fully supports polymorphism, we can detect the real type of the entity with the
instanceof operator (e.g. if(publication instanceof Article)).
We will discuss the PublicationManager SB next. As it was mentioned, this SB groups
together all operations involving articles and books (which are subclasses to Publication). First, we
will briefly skim through the remote interface, PublicationManagerRemote.java.
22 To use a dynamic query instead of a named query, the createQuery(…) method should be used, with a string
parameter containing the JPQL query string.
23 There are two types of query parameters in JPQL. Query parameters starting with a colon (e.g. :authorName) are
called named parameters, because they are identified by their name. However, query parameters starting with a
question mark followed by a number are called positional parameters (e.g. SELECT a FROM Author a WHERE
a.lastName LIKE ?1 AND a.firstName LIKE ?2). These parameters are replaced in the setParameter(…)
method by putting the position number as the first parameter of the method (e.g. query.setParameter(1,
“Smith”)). However, positional parameters are error-prone, so the developer should use named parameters when
possible.
33
package buslogic;
import java.util.List;
import javax.ejb.Remote;
@Remote
public interface PublicationManagerRemote {
void addArticle(String title, int year, String desc, List<Integer> authors,
String categoryLabel, String doi, int idJournal);
void addBook(String title, int year, String desc, List<Integer> authors,
String categoryLabel, int pages, String isbn, byte[] cover,
int idPublisher);
void updateArticle(int idArticle, String title, int year, String desc,
String doi, String category, Integer idJournal, List<Integer> authors);
void updateBook(int idBook, String title, int year, String desc, int pages,
String isbn, byte[] cover, String category, Integer idPublisher,
List<Integer> authors);
void deleteArticle(int idArticle);
void deleteBook(int idBook);
List<String> retrieveArticle(int idArticle);
List retrieveBook(int idBook);
List<Object[]> findPublicationByTitle(String pubTitle);
List<Object[]> findPublicationByYear(Integer yearStart, Integer yearEnd);
List<Object[]> findArticleByDOI(String doi);
List<Object[]> findBookByISBN(String isbn);
List<Object[]> findPublicationByAuthor(String authorName);
List<Object[]> findPublicationByCategory(String categoryLabel);
List<Object[]> findArticleByJournal(String journalTitle);
List<Object[]> findBookByPublisher(String publisherData);
}
As it can be seen, this interface defines methods for adding, updating, deleting and retrieving
articles and books. The rest of the interface lists methods used for searching articles and/or books
by providing various kinds of information, like the publication's title, the article's DOI or book's
ISBN. Some of them even join two entities (e.g. find all publications written by an author, etc.).
One specific detail about publications, articles and books is that when they are created, they
must be simultaneously connected to their related entities: to author(s), to a category and to a
journal (it it's an article) or publisher (if it's a book). This fact complicates the SB methods
considerably. Let's see some of the methods of PublicationManager.java.
public void addArticle(String title, int year, String desc,
List<Integer> authors, String categoryLabel, String doi, int idJournal) {
Article article = new Article();
article.setTitlePub(title);
article.setYearPub(year);
article.setDesc(desc);
article.setDoi(doi);
// create the relationships with Authors
Iterator<Integer> it = authors.iterator();
Author author;
int idAuthor;
while(it.hasNext()) {
idAuthor = it.next().intValue();
author = em.find(Author.class, idAuthor);
article.addAuthor(author);
author.addPublication(article);
34
}
// create the relationship with the Category
Category category =
(Category)em.createNamedQuery("findCategoryByLabel")
.setParameter("categoryLabel", categoryLabel).getSingleResult();
article.setCategory(category);
category.addPublication(article);
// create the relationship with the Journal
Journal journal = em.find(Journal.class, idJournal);
article.setJournal(journal);
journal.addArticle(article);
em.persist(article);
// add the Article to the database
}
This method creates an Article and adds it to the database. The first part of the method is
pretty straightforward: it sets the basic fields of the entity from data passed down as parameters
(like setting the article's title, year of publication, DOI, etc.). The interesting part starts when it must
also set the relationships with other entity types. However, it can be seen that setting the
relationships are fairly easy, because it only involves the calling of the corresponding setter (starting
with the set word, e.g. setCategory(…)) or helper methods (starting with the add word, e.g.
addAuthor(…)). It must be noted, however, that the relationship must be set on both sides of the
relationship, for example, to connect an Article with an Author, the author must be added to the
article with article.addAuthor(author), and then the article must be added to the author with
author.addPublication(author). When all relationships are defined, the article can be persisted
to the database.
Deleting an article from the database is also more complicated, because it's connected to
other entities. This is how the deleteArticle(…) method looks like.
public void deleteArticle(int idArticle) {
Article article = em.find(Article.class, idArticle);
// first remove the relationship with the Category
Category category = em.getReference(Category.class,
article.getCategory().getIdCat());
category.getPublications().remove(article);
article.setCategory(null);
// then remove the relationship with the Journal
Journal journal = em.getReference(Journal.class,
article.getJournal().getIdJournal());
journal.getArticles().remove(article);
article.setJournal(null);
// lastly, remove the relationship with the Authors
for(Author author : article.getAuthors()) {
author.getPublications().remove(article);
}
article.getAuthors().clear();
}
// finally, delete the Article
// must use the merge() method first to update all references
em.remove(em.merge(article));
35
While adding an article required to also set up the relationships before persisting it to the
database, deleting an article requires to remove the relationships prior to deleting it from the
database. In fact, trying to delete an article with the relationships intact will result in errors and
exceptions during runtime. Thankfully, removing the relationships is easy. The method can be
divided into three steps: first find the article to be deleted (using the find(…) method), then remove
the relationships (with the category, journal and the authors), and finally, delete the article using the
remove(…) operation. Before calling the remove(…) method, the references should be updated first,
that's why the article is first updated with the merge(…) operation, and then removed.24
The updateArticle(…) method is a little more complicated, because it supports not only
the update of the article's data, but also its relationships. In a way, its management of relationships
is a combination of the last two described methods: it needs to remove the old relationships first and
then it must add the new ones. The other methods of PublicationManager.java also work
similarly to the methods already shown in AuthorManager.java.
The remaining SBs (JournalManager, PublisherManager and CategoryManager) are very
similar to the already presented SBs. JournalManager and PublisherManager are similar to
AuthorManager. CategoryManager, on the other hand, is more similar to PublicationManager,
because it also has a twist: recursive relationship. If the reader has already a good grip on managing
relationships during CRUD operations, it shouldn't pose a problem. There is, however, a peculiar
feature in Category management. Namely, when the user deletes a category from the database, what
will happen with its subcategories? To answer this question, the deleteCategory(…) method from
CategoryManager.java must be shown.
public void deleteCategory(String label) {
Category categoryToDelete =
(Category)em.createNamedQuery("findCategoryByLabel")
.setParameter("categoryLabel", label).getSingleResult();
Category categoryParent = categoryToDelete.getParent();
// must remove the relationships (with parent and children) first
categoryToDelete.setParent(null);
if(categoryParent != null) {
categoryParent.getSubCategories().remove(categoryToDelete);
}
for(Category categoryChild : categoryToDelete.getSubCategories()) {
categoryChild.setParent(categoryParent);
if(categoryParent != null) {
categoryParent.addSubCategory(categoryChild);
}
}
categoryToDelete.getSubCategories().clear();
// HashSet.clear()
// now delete the Category
em.remove(em.merge(categoryToDelete));
}
Observant readers will surely find out the answer to the previous question just by checking
out this code. One answer to the question should be to also delete the subcategories, when their
parent is deleted. However, in this case, the children will be preserved by relinking them to their
24 One more interesting operation should be the getReference(…) method of EntityManager. This method is
practically the same as the find(…) method, with one important difference: while the find(…) method returns the
whole entity with all its data at once, the getReference(…) method returns only the entity's reference first. As
such, it's much lighter and faster. It's used when we don't really need to manipulate with the entity. It's practically a
lazy retrieval, as the data of the entity will be retrieved only when really needed.
36
“grandparent” instead. Of course, if the parent to be deleted is a root category (meaning that the
category has no parent), then it's obvious that the children have no “grandparent”, and as such, they
should become root categories. This is clearly shown in the source code, too.25
This concludes the discussion about the business logic layer. In the last section of this
chapter, the presentation tier will be presented.
25 The deleteCategory(…) method also has two more interesting details. The first is the grouping of operations in
one line (createNamedQuery(…).setParameter(…).getSingleResult()). This grouping is perfectly legal,
and it could be used in previously shown methods, too, but it's somewhat harder to understand, so it was omitted
previously. The second peculiarity is the getSingleResult() method of Query. This operation is similar to
getResultList(), but it returns only a single result (as such, the type of the returned result will be the type of the
entity itself, not a List). The reader should be warned, however, that it's also more restrictive. The
getResultList() method returns a List with zero, one or more elements (results). The getSingleResult()
method, on the other hand, returns only a single entity. Because of this, finding more than one entity matching the
search, or finding no entities, will result in a NonUniqueResultException or NoResultException,
respectively.
37
The Presentation Layer
The presentation layer is the top layer of the four-tier layered architecture. It's practically the
client tier, because usually only clients use it. It's responsible for rendering some kind of a graphical
user interface (GUI for short), i.e. the front end of the application. Its task is to interact with the
user, acknowledge the user's requests, collect required data, and pass it all down to the business
logic layer for processing. It's also responsible for displaying information which was returned from
the business logic layer.
This layer is typically written in a language with presentation technology support. In the
world of Java, there are many such technologies, such as Swing, SWT, JSP, JSF, etc. In the case of
pubDB, the Java J2SE Swing technology was used. To create this layer in Eclipse, creating a new
Java project with projectClient as its name will suffice.
The presentation layer has two packages. The gui package contains the Swing graphical
components, like windows and dialogs. This package is responsible for interacting with the user,
collecting user requests and data, and also displaying information that was returned from the
business logic layer. However, the classes of this package don't directly interact with the business
logic layer. Instead, they communicate with the classes of the second package, agency. This
package serves as an agent, mediator or middleman between the presentation layer's GUI and the
business logic layer's EJBs. So, in reality, the GUI never actually communicates with EJBs, and
vice versa. This design choice was made to somewhat simplify the GUI, and to further separate it
from the business logic layer. The class diagram of these packages can be seen on Image 7.26
As this term paper is not about presentation, we won't present implementation details from
the gui package. It's sufficient to say that the main class of the gui package, and the whole client
layer is MainWindow.java. This class has a main method, so the whole client starts with this class.
The other classes are practically various dialogs that are called from MainWindow.java, or from
other dialogs. These classes have a Dialog suffix in their names.27 Additionally, one class serves as
a helper class: ComponentFactory.java. This class is practically a factory class, i.e. it creates some
specific Swing components that are frequently used across the GUI. For example, if the GUI
frequently uses a button with fixed dimensions, color and text, it would be a good choice from a
design perspective to move the creation of this button to a factory class and pre-customize it, so
when a dialog needs a button like that, it calls the factory to create the button instead. Also, this
avoids code duplication, because dialogs don't need to customize the button every time when it's
needed.
One more thing to mention about these dialog classes is that they are parametrized (except
Their constructor, that creates and sets up the dialog has one parameter.
These dialogs can function in two “modes”: add mode and view/update mode. The dialog works in
add mode when the parameter is null. In this case, the user can add a new entity (such as an author,
category, journal, publisher, article or book) into the database. On the other hand, the dialog will
work in view/update mode when a valid parameter containing the entity's ID is provided. In this
mode, the user can view an existing entity retrieved from the database, and even update its data. In
SearchDialog.java).
26 It should be mentioned that there are two more additional packages in the presentation layer: img and test. The
img package is practically a folder to images and pictures needed for the GUI, and as such, it doesn't contain Java
classes. The test package is a package which was in use in the development phase. It contains only one Java class,
which was used for testing in the command prompt. However, because neither of these packages are important, it
was decided to omit them completely.
27 Honestly, not just MainWindow.java has a main method, but all dialogs, too (classes with the Dialog suffix, e.g.
AuthorDialog.java). However, a user should never start the application from a dialog. The reason that these
dialogs have a main method is a choice made by WindowBuilder Pro, the tool which was used to create pubDB's
GUI.
39
this case, the dialog transforms a little: all data fields will be filled with the entity's data, the Add
button will change to Update, etc. We will see this more closely when we'll show the application in
action.
Image 7: Class diagram of the gui and agency packages
From the aspect of EJB 3, it's more interesting to show the inner details of the agency
package. As it was told, the GUI uses this package to communicate with the SBs in the business
logic layer. As such, it functions more like an agent, mediator or middleman between them. This
package consists of the mediator itself called Agent.java and several helper classes to aid this
class. Let's see portions of Agent.java to see how it works.
package agency;
import javax.naming.NamingException;
import …
// some other imports
public class Agent {
public static void addAuthor(String lastName, String firstName,
String biography) {
try {
AuthorSession.getAuthorManager().addAuthor(
lastName, firstName, biography);
} catch (NamingException e) {
e.printStackTrace();
}
}
40
public static void updateAuthor(int idAuthor, String lastName,
String firstName, String biography) {
try {
AuthorSession.getAuthorManager().updateAuthor(
idAuthor, lastName, firstName, biography);
} catch (NamingException e) {
e.printStackTrace();
}
}
public static void deleteAuthor(int idAuthor) {
try {
AuthorSession.getAuthorManager().deleteAuthor(idAuthor);
} catch (NamingException e) {
e.printStackTrace();
}
}
public static List<String> retrieveAuthor(int idAuthor) {
try {
return AuthorSession.getAuthorManager().retrieveAuthor(idAuthor);
} catch (NamingException e) {
e.printStackTrace();
return null;
}
}
public static List<Object[]> populateAuthorList() {
try {
return AuthorSession.getAuthorManager().populateAuthorList();
} catch (NamingException e) {
e.printStackTrace();
return null;
}
}
public static List<Object[]> findAuthor(String authorName) {
String revisedAuthorName = reviseSearchString(authorName, true);
try {
return AuthorSession.getAuthorManager().findAuthor(revisedAuthorName);
} catch (NamingException e) {
e.printStackTrace();
return null;
}
}
private static String reviseSearchString(String searchString,
boolean areTwoFields) {
String revisedSearchString = "%";
revisedSearchString = revisedSearchString.concat(searchString);
revisedSearchString = revisedSearchString.concat("%");
if(areTwoFields) {
revisedSearchString = revisedSearchString.replace(' ', '%');
}
return revisedSearchString;
}
// all other methods involving categories, journals, publishers, articles and
// books
}
41
In this class, all calls to SBs in the business logic layer are grouped together. However, for
demonstration purposes, it's sufficient to show only the calls towards the AuthorManager SB, and
calls toward the other SBs (like CategoryManager, PublicationManager, etc.) are omitted. Also,
as it can be seen, all methods of this class are static.
Let's explain, how SBs can be used from a non-J2EE (that is, J2SE) environment. Observant
readers will notice that practically all methods of Agent.java use the same methodology, so it's
sufficient to demonstrate this on only one method, for example on the addAuthor(…) method.
Simply speaking, this method gets a reference to the AuthorManager SB, and calls its
addAuthor(…) service (that is, method). To get a reference to AuthorManager, the method uses a
helper class from the agency package called AuthorSession.java. Let's see how this class looks
like.
package agency;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import buslogic.AuthorManagerRemote;
public class AuthorSession {
private static AuthorManagerRemote sessionAM = null;
public static AuthorManagerRemote getAuthorManager() throws NamingException {
if(sessionAM == null) {
Context context = new InitialContext();
sessionAM = (AuthorManagerRemote) context.lookup("authorManager");
}
return sessionAM;
}
}
As it can be seen, this class is very simple. To get a reference to the AuthorManager SB, the
SB's remote interface will be used. This implies that JNDI must be used to get the actual reference.
First, an instance of the JNDI initial context object must be obtained, which is of type
InitialContext. An InitialContext object can be used to connect not just to a local JNDI
server, but also to a remote one. After the JNDI initial context was obtained, its lookup(…) method
should be called to actually get the reference of the SB. Its parameter should be the JNDI name of
the SB. As we recall from AuthorManager.java, the name of this SB was set in the @Stateless
annotation: @Stateless(mappedName = "authorManager"). So, by setting the parameter of the
lookup(…) method to "authorManager", a reference of this SB will be obtained. Of course, the
object that was obtained via JNDI must be cast to the appropriate type (in this case, to
AuthorManagerRemote). It should be also noted that the lookup(…) method throws a
NamingException.
We can return now to the Agent.addAuthor(…) method. In this method, after obtaining a
reference to AuthorManager from AuthorSession, its services are ready to be called. In this case,
the SB's addAuthor(…) method will be called. As it can be seen, this is all a developer needs to do
to use a service of a SB. However, because the lookup(…) method throws a NamingException, the
Agent.addAuthor(…) method must catch it. That's why all methods in Agent.java are surrounded
with a try/catch block.
One more interesting detail about the Agent.java class is that is has a private method
42
called reviseSearchString(…). This method is called from other Agent methods, more precisely,
from search methods (starting with a find prefix, e.g. findAuthor(…)). The purpose of this method
is to revise the text entered in GUI search text fields by a user. It adds some additional characters to
the search string, namely the percentage “ %” sign. In JPQL, the LIKE operator supports “joker
marks”. Adding an underscore “_” sign to the string replaces one character on that place, e.g.
specifying “_ike” will find “mike” “bike” or “like”. However, the percentage “%” sign can
substitute a random number of characters, e.g. specifying “ my%friend” will find “my friend”, but
also “my good friend”, “my best friend” and “my only friend”. This method revises the search string
by adding the “%” character at the beginning and the end of the string, and also by replacing any
space characters with “%”. This improves the quality of the search considerably. For example, the
“engineering” string will be revised to “%engineering%”, so besides finding “Engineering”, the
search will also find “Software Engineering”, “Field Engineering” and “Engineering of Critical
Systems”.
Image 8: The JMX Console
One can ask, is there a way to see the contents of JNDI, i.e. to see which objects are
registered in JNDI and what is their name. Thankfully, JBoss allows this by accessing the JBoss
Management Console called JMX Console, which provides a raw view into the microkernel of the
43
JBoss AS. The console can be accessed by entering http://localhost:8080/jmx-console into a
browser (of course, the JBoss Application Server must be started first). In the console, find the
jboss category, and click on service=JNDIView. A new page similar to Image 8 should be displayed.
There, find the list operation in the operations table and click on its Invoke button (in the
Parameters column). The JMX MBean Operation View page will be displayed. In the list, browse
down until you find Global JNDI Namespace. This is how a portion of it should look like:
…
…
+++++-
authorManager (class: Proxy for: buslogic.AuthorManagerRemote)
categoryManager (class: Proxy for: buslogic.CategoryManagerRemote)
publicationManager (class: Proxy for: buslogic.PublicationManagerRemote)
publisherManager (class: Proxy for: buslogic.PublisherManagerRemote)
journalManager (class: Proxy for: buslogic.JournalManagerRemote)
As it can be seen, it also contains the SBs of pubDB (among many other names that are not
important for us). For example, the authorManager name can be used to get a reference to
AuthorManagerRemote. Note that
specifying a nonexistent name will throw a
NameNotBoundException during runtime.
This concludes the presentation of the implementation of this practical example. In the final
chapter of this paper, it will be shown, how the example looks like when it is executed.
44
The Practical Example in Action
In this chapter, we will present the practical example, pubDB in action. However, the
application should be configured properly before executing. Because of this, this chapter has two
sections: the first briefly explains how pubDB should be configured in a localhost environment, and
the second shows the application itself.
Setting up and Configuring pubDB
To configure pubDB in a localhost environment, first the JBoss Application Server should be
configured with MySQL.28 In order to achieve this, JBoss AS needs a MySQL driver. This driver is
called MySQL Connector/J, and it can be obtained from MySQL's website29. For pubDB, version
v5.1.6 was used (named mysql-connector-java-5.1.6-bin.jar), however, at the time of writing
this paper, the newest version should be v5.1.16. Nevertheless which version is used, the driver
should be copied into the JBOSS_HOME/server/default/lib folder. The second task would be to
configure the datasource. A datasource is used to configure the parameters for communicating with
a specific database (in the case of pubDB, JBoss AS should communicate with MySQL). A
datasource usually has some pretty basic parameters like name, host name, database name, username
and
password.
The
datasource
file
should
be
copied
into
the
JBOSS_HOME/server/default/deploy folder. The datasource of pubDB is an XML file named
mysql-ds.xml and should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
<jndi-name>MySqlDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/seds</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>robi</user-name>
<password>robi</password>
<exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
The user-name and password tags should be the user-name and password of the user which
was created in MySQL (as mentioned earlier at the beginning of the previous chapter). The
connection-url tag should be the address of the server, starting with jdbc:mysql://, following
with the address itself (it's localhost here, but it can be a remote server on the network) and the
port (usually 3306) separated with a colon, and finally the name of the schema itself (the creation of
a schema was also mention at the beginning of the previous chapter) preceded with the “/” sign.
28 It should be mentioned that there is a bug in JBoss AS 5.1.0 GA that prevented the starting of the server for some
versions of JDK as described at https://issues.jboss.org/browse/JBAS-6981. To solve this, edit the
JBOSS_HOME\server\default\conf\bootstrap\profile.xml file. Find there the definition of
AttachmentStore, and replace <constructor><parameter> with
<constructor><parameter class="java.io.File">. The bug was fixed in JBoss AS v6.0.0.M1.
29 The driver can be downloaded from MySQL's website: http://dev.mysql.com/downloads/connector/j/
45
Now it's time to set up the JBoss server itself in Eclipse. First, a new server should be
created in Eclipse, which can be done in the Server tab in the Java EE perspective. JBoss v5.0
should be selected as the server's type, localhost as the host name, 8080 as its port, and for the
server's runtime environment the developer should find the folder where JBoss was unpacked.
Now that the server is set up, it's time to configure the EJB 3 deployment descriptor. It was
mentioned in the introductory chapter about EJB 3, that deployment descriptors are practically
never used (contrary to EJB 2). However, some basic configuration must be done. Of course,
Eclipse modifies it as new entities are added to the persistence layer. The deployment descriptor is
practically an XML file named persistence.xml and it can be found in Eclipse inside JPA Content
of the JPA project (in the case of pubDB, projectJPA → JPA Content → persistence.xml). Besides
manually editing the XML file, Eclipse provides a graphical interface for this purpose. In the
Connection tab, the JTA data source field should be set to java:/MySqlDS (it's the name of the
datasource configured earlier), and in the Properties tab, add the following two properties in the
table: hibernate.hbm2ddl.auto (with update as its value) and hibernate.show.sql (with true
as its value). These two properties set Hibernate as EJB's persistence framework and show the SQL
queries in the debug console (the second property is not really necessary). In the Source tab, the
developer can edit the file manually. Here's how it should look like (after adding all entities):
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="projectJPA">
<jta-data-source>java:/MySqlDS</jta-data-source>
<class>persistence.Author</class>
<class>persistence.Publication</class>
<class>persistence.Article</class>
<class>persistence.Book</class>
<class>persistence.Journal</class>
<class>persistence.Publisher</class>
<class>persistence.Category</class>
<properties>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show.sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
Contrary to EJB 2's verbose deployment descriptor, EJB 3's descriptor looks very simple.
Note, that Eclipse adds managed classes (in the class tag) automatically as new entities are
created.
The last step in the configuration should be the configuration of the client. Select the main
class which is used to start the application (in this case, MainWindow.java), right-click on it, and
select Run As → Run Configurations. Select the Arguments tab and enter this in the VM arguments
box:
-Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
-Djava.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
-Djava.naming.provider.url=localhost
With this, the configuration of pubDB is complete.
46
Starting pubDB
PubDB can be started by first starting the created server in Eclipse, and then by running
The remainder of this section will show, how the application looks like, and
how it is used. Several screen-shots will be inserted at various places to help the reader visualize the
application.
MainWindow.java.
Upon starting pubDB, the main window will be shown (Image 9) with several buttons to add
a new entity to the database (new author, category, journal, publisher, article or book) and an
additional button to search the database for already added entities. The lower part of the window
show help messages to aid the user. When the user places the mouse cursor over a button, the help
message automatically changes to show a text explaining the purpose of that particular button.
Image 9: The main window of pubDB
When the user clicks on the Add Author button, the Add New Author dialog appears (Image
10). Here, the user can enter the author's first and last name, and optionally even his or her
biography. To add an author to the database, the user must click on the Add button. It must be noted,
that the author's first and last name must be provided prior to adding the author. If the user leaves
one of these text fields empty, the application will display an error message (Image 11). Otherwise,
the application will add the author to the database and close the dialog, returning the user back to
the main window.
The Add Category button adds a new category into the database. In the Add New Category
dialog (Image 12), the user must enter a label for the category (otherwise, an error message will be
displayed) and select its parent from the tree. The root of the tree is called Root, and it's
automatically selected. If the user doesn't specify a parent category from the tree, the application
will assume that the user wants to add the category as a root parent category. Note, that the Root
node of the tree is actually not a real category, however, its label is reserved. Because of this, if the
user tries to add a category with Root as its label, an error message will appear explaining that the
47
label is reserved. There is one more restriction that must be mentioned: the label of the
category should be unique, so, if a category with the same label already exists in the database, the
new category won't be persisted and an error message will be displayed.
Root
Image 10: The Add New Author dialog
Image 11: An Error message showing that the author cannot be added to the database
With the Add Journal button from the main window, the user can add a new journals to the
database. In the Add New Journal dialog (Image 13), the user can enter the journal's title and
volume. Filling out the journal's title is mandatory, an error message will be displayed otherwise.
The Add New Publisher dialog is almost the same (opened by clicking on the Add Publisher button
in the main window). Here, the user can enter the publisher's name (which is mandatory) and
address (which is optional).
48
Image 12: The Add New Category dialog
Image 13: Both the Add New Journal and Add New Publisher dialogs are very similar
Clicking on the Add Article button in the main window will show the Add New Article dialog
(Image 14). This dialog is more complex compared to the previous dialogs, because it offers more
fields to be filled and because this is actually the dialog where an article should be linked with other
entities, like author(s), a category and a journal. Because of this, before the Add New Article dialog
is actually displayed, pubDB must retrieve all existing authors, journals and categories from the
database. Once the dialog is displayed, the user can enter the article's name (mandatory), year of
49
publication (mandatory), DOI (optional) and abstract (optional). Also, the user should select one or
more authors from the Author(s) list30, one journal from the Journal combo box, and one category
by clicking on the Select button. By clicking on this button, a small dialog will be displayed, where
the user can select the adequate category for the new article (Image 15). Of course, prior to adding
an article, the user can decide to add a new author, journal or category to the database directly from
the Add New Article dialog by clicking on the corresponding Add buttons. Similarly, the already
existing authors, journals and categories can also be viewed (and even updated if necessary) by
clicking on the corresponding View buttons (the View button for categories is placed on the category
chooser dialog (Image 15). If the user adds a new author, journal or category to the database, or
updates an existing one, the corresponding list (for authors), combo box (for journals) and tree (for
categories) will be automatically refreshed with new data. The dialogs responsible for viewing and
updating these entities will be presented a bit later. The user can get error messages when the
article's title and year of publication is not provided, when the user has forgotten to connect the
article with an author (or authors), a category and a journal, or when the year of publication field is
not a positive number.
Image 14: The Add New Article dialog
Clicking on the Add Book button in the main window will show the Add New Book dialog.
This dialog is very similar to the Add New Article dialog. Namely, the book's title, year of
publication, author(s) and category is the same as for an article (its description is also the same, but
it's now referred to as a review, not an abstract). However, there are some differences, too. For
example, a book is connected to a publisher, not a journal (but the combo box looks the same
30 More authors can be selected by holding down the Control key on the keyboard while selecting them.
50
nevertheless). Additionally, a book cannot have a DOI, but it can have an ISBN, the number of
pages, and a cover image. The cover can be selected by clicking on the Browse button. This opens a
file selector dialog, where the user can browse the file system and select a JPG image. When the
user clicks on the Open button, the picture is scaled down (or up) to a specific dimension and
displayed on the screen. Of course, the user can click on the Browse button again to browse for
another picture, or on the Remove button to remove the opened image from the cache. The
restrictions explained in the Add New Article dialog also apply here, so if the user doesn't comply,
error messages will be displayed.31
Image 15: The Category Chooser dialog
Finally, by clicking on the Search button in the main window, the Search dialog will be
displayed. The dialog itself is divided roughly into two panels, the upper panel which is a tabbed
pane, and the lower, where the search results are displayed. The tabbed pane has two tabs: in the
first, the user can search for authors (by their name), categories (by their label), journals (by their
title) and publishers (by their name and/or address). By selecting the second tab, the user can search
for articles and books (by their title, year of publication, author or category), for specific articles (by
their DOI or journal) and for specific books (by their ISBN or publisher). The user should select the
type of the search by first selecting the appropriate radio button and then entering the search string
into the appropriate text field.
By clicking on the Search button, the search process will commence querying the database,
and the results (if any) will be displayed in a table in the lower panel (Image 16). The table has
three columns: the first displays the entity's ID (primary key from the database), the second shows
the entity's type (author, category, journal, publisher, article or book), and the third shows some
basic information about the entity. The user can click on a row in the table to select an entity. When
an entity is selected from the table, the user can decide to view it or to delete it. By clicking on the
View button, the appropriate dialog will open, showing all information from the database. For
example, if the user has selected an entity of type author, the author dialog will appear (Image 17).
Observant readers will surely notice, that this dialog (which is named View and Update an Existing
Author) is very similar to the Add New Author dialog. This is absolutely true, because only one
dialog (and one Java class) is used for both adding and viewing. We mentioned earlier, that most
dialog classes in pubDB are parametrized. Depending on the parameter, the dialog can function in
two modes: add mode and view/update mode. By comparing Image 10 with Image 17, the
following visual changes can be observed:
31 The only addition is that the Add New Book dialog will also check whether the entered number of pages is valid, that
is, a positive number.
51
•
the fields are automatically filled with data retrieved from the database (in view/update
mode);
•
the dialog titles are different (Add New Author versus View and Update an Existing Author);
•
the action buttons are different (Add versus Update); and
•
a new panel is added showing the author's publications in a list (this panel is hidden in add
mode).
Image 16: The Search dialog
Of course, the user can modify the viewed entity by clicking on the Update button. If no
errors were displayed, the entity is updated and the user is returned to the Search dialog. Note, that
the results table is also updated, if necessary.
52
Image 17: The dialogs for adding and viewing an entity are similar
Image 18: The View and Update an Existing Book dialog
53
Let's see one more entity dialog in view/update mode. Suppose that the user has searched the
database for books, selected a book from the results and clicked on the View button (Image 18). This
dialog (named View and Update an Existing Book) is also very similar to the Add New Book dialog.
Similarly to authors, the title of the dialog and the title of the action buttons are different. However,
instead of showing or hiding a whole panel, this dialog does something else. Namely, it must
display the author(s), category and publisher this book is related to. Also note, that the cover image
is actually retrieved from the database, not from the file system.
To delete an entity from the database, the user should find it by searching, select it from the
results table, and click on the Delete button. It must be noted, however, that deleting an author,
category, journal or publisher which is related to an article or book, will also delete the related
article(s) and/or book(s). This is the way the model of the application works: e.g. an author can
survive without an article, but an article cannot survive without an author, so if the article's author is
deleted, the article itself must be also deleted. If pubDB notices, that the entity to be deleted is
related to an article or book, it will display a confirmation message informing the user that the entity
is related to an article or book, and asking whether the user really wants to delete both entities.
When the user clicks on the Yes button, pubDB first deletes the related article(s) and/or book(s), and
then it deletes the original entity.
This concludes the presentation of the Publication Database.
54
Conclusion
Building enterprise applications, a part of an information system, is not easy. Several
characteristics should be met, like performance, scalability and robustness. However, nowadays,
there is one more feature that is considered to be more and more important: the application should
be easy to use. There are many players on the field, and one of them is Oracle's technology called
Enterprise JavaBeans (EJB for short). EJB's history can be considered as quite eventful. EJB 1 was
very innovative, but not very functional. This was corrected in EJB 2, however, the technology
itself became so complicated that only the bravest dared to exploit its potential. At the same time,
some lightweight alternatives emerged, becoming more and more popular. As EJB's popularity
plummeted, it was clear that the next reincarnation of EJB must be something revolutionary. And
that's exactly what happened. EJB 3 was lightweight, easy to use and functional.
The goal of this term paper was to introduce EJB 3 to the reader, both theoretically and
practically. For the practical part, an example called Publication Database (or pubDB) was
developed. To demonstrate the possibilities of EJB 3, the example was taken apart. First, the model
of the system was analyzed. After that, implementation details were presented, demonstrating the
easiness of EJB 3. Lastly, the example was also shown in action.
However, there is always room for improvement for pubDB. First of all, the data (fields) for
entities are fairly simple, and may need to be widened. But probably the most important task would
be to optimize the communication of pubDB with the database. This means that the amount of data
transmitted and received could be lower. There are also some very demanding queries. Queries
returning entire tables should be avoided when possible. PubDB contains some queries such as
these. They are primarily used in the dialogs involving articles and books (e.g. before showing the
Add New Article dialog, the application retrieves all articles and journals to populate the
corresponding list and combo box). A redesign of these dialogs would solve the problem. For
example, the list of authors could be modified to also include a text box where the user can enter a
search string, and clicking on a button, the list of authors would be populated only with authors
matching the query, not with all authors. However, as long as the database is not too big, the actual
solution is also very functional.
To summarize, it should be clear by now, that developing enterprise applications in EJB 3 is
very user-friendly, and because of that, results come fast. Only one question remains: which path
will be chosen for the next iteration of EJB? However, because the actual incarnation of EJB is so
widely accepted, it should be natural to follow the path which is considered to be popular and welltried.
55
References
Enterprise JavaBean 2011, Wikipedia, The Free Encyclopedia, viewed 18 May 2011,
<http://en.wikipedia.org/wiki/Enterprise_JavaBean>
Enterprise JavaBeans Technology 2011, Oracle, viewed 18 May 2011,
<http://www.oracle.com/technetwork/java/javaee/ejb/index.html>
Panda, D, Rahman, R & Lane, D 2007, EJB 3 in Action, , Manning, Greenwich
57