Last month, we began to look at Enterprise JavaBeans (EJB), the centerpiece of Sun's J2EE (Java 2 Enterprise Edition) standard for server-side web applications. While neither the Java language nor the J2EE specification are open standards, increasing numbers of Linux advocates have begun to use them to write server-side web applications. The fact that J2EE appears to be the only mainstream alternative to Microsoft's .NET framework makes it even more appealing to many.
Enterprise JavaBeans come in two basic flavors, known as session beans and entity beans. Session beans typically model processes and lack any state, allowing us to place our business logic in EJB, rather than in our servlets or JavaServer Pages (JSPs). The calculator object we designed last month, which allowed us to multiply two numbers, was a simple example of a session bean with a single method.
Entity beans are meant to contain state, possibly even complex state. This state normally reflects the contents of a row in a relational database, with the bean managing its own object-relational mapping (bean-managed persistence or BMP) or allowing EJB to handle this task instead (container-managed persistence or CMP). The EJB container also provides transactions, giving us all-or-nothing operations in our objects as well as the database.
This month, we write a simple entity bean, connect it to a database and access it via a session bean from a Java application. We will use the open-source JBoss EJB container (released under the GNU Lesser General Public License, aka LGPL), but the code should work with little modification on any J2EE server that supports EJB.
As we saw last month, writing a session bean really means writing three Java classes:
The bean class performs the actual work.
The remote interface has methods that match those on the bean class and is our proxy to the bean.
The home interface helps us to create new instances of the bean class, as well as search for beans matching particular criteria.
We need to implement all three of these classes for an entity bean. In addition, we often need to implement a fourth “primary key” class. While this month's example does not need to define a primary key class, we will do so in the interest of completeness.
Most EJB applications will end up using at least one entity bean (to model the data) and at least one session bean (to implement the business logic). Given that a core idea of object-oriented programming is to put data and code in a single package, it seems a bit strange to split entity and session beans in this way. But this strategy does seem to work overall and makes it relatively easy to split work among multiple people, once the specification has been agreed upon.
J2EE is a specification; the actual implementation of that specification depends on whoever has written the server. One of the most important parts of a J2EE application server is the object-relational mapper, which transparently turns Java classes into rows of a relational database table (and vice versa). An object-relational mapper should remain as invisible as possible, allowing us to change our back-end storage from Oracle to MySQL without modifying our Java code.
The JBoss object-relational mapping system is known as JAWS and normally requires very little configuration. However, it can be instructive to look at the JAWS configuration file (standardjaws.xml, in the JBoss conf/default directory) to see what's happening behind the scenes.
The definitions at the top of standardjaws.xml set parameters for the entire JBoss server. In this way, we indicate which database we want to use; the HyperSonic database is included with JBoss, and we will use it for this month's examples.
The core of standardjaws.xml is the multiple <type-mapping> (singular) sections, which connect each <java-type> to a <jdbc-type> and an <sql-type> for each database. Since our EJBs do not create tables or write SQL explicitly, it's important that these values be accurate. You may be able to increase the efficiency or flexibility of your application by modifying these values. However, remember that modifying JAWS after you already have inserted data into a database may lead to confusion, corruption or errors.
If you simply want to get started with EJB, then you won't need to modify standardjaws.xml at all. Rather, you'll need to modify jboss.jcml, an XML file that defines the different managed beans (MBeans) that JBoss uses for system configuration and control.
The file jboss.jcml includes support for HyperSonic and InstantDB; in order for it to work with HyperSonic, I had to remove any reference to InstantDB from jboss.jcml. I did this by editing the “JDBC” section of jboss.jcml, removing the mention of org.enhydra.instantdb.jdbc.idbDriver from the “Drivers” attribute for the JdbcProvider MBean and the entire XADataSourceLoader <mbean>, whose service is XADataSource and whose service name is InstantDB.
Once you have removed all mentions of InstantDB from jboss.jcml, start up JBoss:
cd $JBOSS_DIST/bin sh run.sh
Our entity bean models a single book, where each book has a title, an author, a publisher and a price. For the sake of simplicity and space, we will ignore the possibility that a book might have multiple authors or publishers. We also will avoid normalizing the data, which would mean having instance variables that are themselves entity beans.
We begin by implementing the BookBean class, depicted in Listing 1 [available at ftp.linuxjournal.com/pub/lj/listings/issue93/5577.tgz]. BookBean is a typical simple bean class definition for a container-managed entity bean; it defines a field for each column in the database that we want to trace, including an integer “id” field that serves as a primary key.
We must define the ejbCreate() method to match the signature of the create() method on the home interface. Each time someone invokes create() on the home interface, the EJB container invokes ejbCreate() on our bean class with the same arguments. ejbCreate() is where the real creation action is; while a CMP entity bean doesn't need to worry about handling the object-relational mapping, it does need to set its instance variables to appropriate values.
Other than ejbCreate(), the only methods we must write are the “getter” and “setter” methods for each field, such that other objects can retrieve or modify the field's value. Each method is pretty simple in our example, returning or modifying the value of an instance variable.
Our remote interface, shown in Listing 2, is called Book.java, and its API is almost identical to the bean class. Applications normally will talk to the remote interface; if something goes wrong, it throws a RemoteException.
We also define a home interface, shown in Listing 3, with a create() method that creates a new instance of Book (and implicitly, a new row in our database table) when handed all of a book's details. If we were so inclined, we could offer users multiple versions of create(), each of which would take a different number of arguments.
Notice how our create() method requires that we provide an explicit primary key. Experienced database programmers know that primary keys should be hidden from view, and most databases have a way to automate this; PostgreSQL's SERIAL type, MySQL's AUTO INCREMENT and Oracle's sequences are common solutions to this problem. Unfortunately, there is no easy way to use such automatically generated primary keys within EJB. Therefore, we must set it explicitly (as in this month's examples) or use an external value, such as the ISBN, which could be a String. This is one of the most surprising things that I've found about EJB, and I hope that future versions of the specification will remedy the situation.
The findBy...() methods allow us to locate and retrieve instances of Book. Invoking findByPrimaryKey(5) returns an instance of Book with the primary key 5. All of the findBy...() methods are implemented by the EJB container, saving us from having to do so ourselves. The findAll() method returns a collection of all objects of this type (i.e., all rows in the database table), allowing you to iterate through them.
Unfortunately, automatically defined findAll...() methods use simple equality checks. We cannot use regular expressions or other techniques to search for books whose author field begins with O, or whose publisher begins with A and ends with M. Instead, we must use findAll(), iterating through the returned collection and filtering through them as necessary.
Finally, our primary key class (BookPK.java), shown in Listing 4, defines a single instance variable (id) that acts as our primary key. The equals() method indicates whether two instances of BookPK are identical, allowing the system to compare two instances of Book. The hashCode() method must return a unique value for each instance, which can be the id in this particular case. The toString() method must return a string version of the primary key, which simply returns String.valueOf(id) in our class.
Because all four of these classes are in the il.co.lerner.book package, I placed all four source files (Book.java, BookBean.java, BookHome.java and BookPK.java) in the directory $BOOK/il/co/lerner/book, where BOOK is the root directory of this project.
Now that we have defined our entity bean, we need to describe it to the EJB container. Our deployment descriptor, a file named ejb-jar.xml, is shown in Listing 5. We place ejb-jar.xml in $BOOK/il/co/lerner/book/, alongside the Java classes that will form our entity bean; when we build the bean with Ant, it will be placed in a subdirectory named META-INF.
The most interesting parts of ejb-jar.xml for an entity bean are the <persistence-type> section (set to “Container” for CMP), the <prim-key-field> and <prim-key-class> sections (in which we name the class of our primary key), and the <cmp-field> sections (which describe the container-managed fields to JBoss).
The deployment descriptor is a standard part of EJB and should work across all EJB servers and containers. However, it does not address all of the runtime configuration issues. For JBoss to work correctly, we thus include a file named jboss.xml that tells the server where we can find the beans on the network. A copy of jboss.xml, which we place alongside ejb-jar.xml in $BOOK/il/co/lerner/book/, is shown here:
<?xml version="1.0" encoding="UTF-8"?> <jboss> <enterprise-beans> <entity> <ejb-name>Book</ejb-name> <jndi-name>Book</jndi-name> </entity> </enterprise-beans> </jboss>
The source code for our simple test application, UseBook.java, is presented in Listing 6 [available at ftp.linuxjournal.com/pub/lj/listings/issue93/5577.tgz] and demonstrates how much you can do with very little code. It defines only a main() method and immediately goes about working with the EJBs. First it grabs a JNDI context and looks up the Book bean that we have defined. That allows it to create an instance of BookHome, which in turn lets us create a new instance of our Book bean:
Book book = home.create(testPrimaryKey, "Book title", "AuthorFirst AuthorLast", "PublisherName", 10.50);
As you can see, we've hard-coded the values we will add to the database, including the primary key. This is obviously unacceptable in the real world; a nonexample application would have taken the primary key (and other values) from a file, the command line, an environment variable, an HTML form on the Web or would have generated them automatically.
Notice how we never have to create SQL tables, insert rows or retrieve them. Our back-end persistent storage is presumably a relational database, but our Java client neither knows nor cares.
Once it has inserted a new row into the table, UseBook modifies some of its values (by setting instance variable values) and then retrieves all of the instances of Book in the database (with findAll()). Along the way, it sends status messages to System.out, which are printed on the console.
We use Ant, the Java replacement for the standard make program, to compile and install our program. Listing 7 [available at ftp.linuxjournal.com/pub/lj/listings/issue93/5577.tgz] shows our build.xml file, which compiles each of the .java source files from $BOOK/il/co/lerner/book, turns them into a jar file along with the deployment descriptor and JBoss runtime configuration file, and then installs the files into the JBOSS directory. If Ant is installed in ANT, you can compile all of the files, install them into the JBoss “deploy” directory and invoke UseBook.main() with:
As soon as JBoss notices the new (or updated) jar file, you will see output on the screen indicating what the server is doing. The output from Ant, by contrast, will display all of the items sent to System.out from within main(), including the status messages in Listing 6 that indicate what we have done. Each time you compile and run main() with a new ID value, a new row will be inserted into the database table.
Sun would like you to believe that EJB is the future of all server-side web applications. Microsoft, of course, is doing its utmost to convince you that .NET is the real future. How do independent developers fare in this war of giants, and what should free software advocates do?
The good news is that J2EE is an excellent architecture and philosophy. It's neither the most elegant nor the most flexible, and it does lock you into a single language. But overall, I'm impressed with J2EE and see it as an important milestone in the world of web application programming.
Unfortunately, there are a number of issues with J2EE that arise every time I use it to write an application. The first issue is that neither Java nor J2EE are open source, despite the fact that Java is free of charge and JBoss is licensed under the LGPL. Sun generally has been an honest player, but they are a commercial company with their own interests. Open-source advocates should not be surprised if and when Sun restricts the use of code or specifications.
In addition, the departure of Enhydra Enterprise appears to leave JBoss as the only open-source J2EE application server on the market. JBoss isn't officially certified as J2EE-compliant, however, because the JBoss team cannot pay for official certification. This strikes me as rather shortsighted of Sun; perhaps they could offer free or inexpensive certification for servers released under the GPL or LGPL, which (unlike the Berkeley license) ensure that no company could ever turn a certified server into a proprietary product. Official J2EE certification isn't important to me, but there are many CEOs and CTOs who do require it, which means that JBoss is often ignored for no good reason.
Java and EJB are complex, and it will take time for a programmer to learn about them. But the complexity of Java and EJB programming is dwarfed, in my experience, by a confusing array of configuration files, new terms, environment variables and other items that are unique to Java. Better documentation would certainly help, but it seems to me that a Java analogue to CPAN, which would make server-side Java configuration easier, would let programmers concentrate on programming rather than system maintenance issues.
Finally, the one part of .NET that really appeals to me is its relative openness to different programming languages. By definition, J2EE requires Java, which is often the right choice but should never be the only choice. SOAP and XML-RPC make it possible to bridge the gap between languages but without the nice transactioning and object-relational mapping that EJB brings to the table. For now, it seems that the only way for Python to speak to EJB is via SOAP or XML-RPC (or Jython), but I would love to see other possibilities in the future.
EJB is an impressive technology, doing far more than the simple object-relational mappers Alzabo and DODS. From my experience, working with EJB is more of a managerial and logistical headache than a technical one. Learning EJB is a good idea for all web application developers; it's clear that this standard is making serious inroads in the industry, and many serious applications will be built using EJB in the future. Having certified open-source implementations will make it even easier for programmers to try out EJB, and I encourage Sun to move in this direction as soon as possible.
Next month, we'll switch gears to begin looking at Zope, a very different type of web application framework written mostly in Python. Zope has become quite popular in the last few years and is often seen as the killer app that will bring Python to the forefront of programming languages. We'll take a look at Zope and start to examine how we can use it to write our own applications.