Building a Solid Foundation for JPA and Hibernate

来源:互联网 发布:防止xss攻击php函数 编辑:程序博客网 时间:2024/05/29 13:52

 

Building a Solid Foundation for JPA and Hibernate

By Stephen B. Morris

Date: Feb 16, 2009

Return to the article


Befuddled by your Java persistence code? Trying to get on top of JPA and Hibernate? These topics can be mastered easily, as veteran developer Stephen B. Morris illustrates here with fully worked examples and key techniques.


Converging Software

Looking at the latest trends in enterprise software development, I see a number of emerging patterns. Many organizations are aggressively pursuing a policy of employing lower-cost labor. Simultaneously, enterprise development is becoming ever more complex. These two forces are in direct opposition.

Not only is development complexity increasing, but another element is at work—what might be called convergent software. In a sense, the layers of development technology are merging. For an example of this trend, you need look no further than technologies such as JPA, Hibernate, and EJB3. Each of these technologies represents a confluence of what were previously separate disciplines:

  • The Java Persistence API (JPA) uses an annotation-rich environment to conjoin Java and persistence coding.
  • Native Hibernate provides much of the same capability as JPA, while also facilitating direct database interaction.
  • Enterprise JavaBeans 3.0 (EJB3) provides an incredibly simple but powerful unified model for bean modeling, persistence support, web services, etc.

In my opinion, these trends are welcome because they help to flatten the hierarchy of developers. If Developer X has previously focused on an area such as web services development, for example, it's feasible for Developer X to master persistence development.

Such skill diversification is no longer a choice—as developers, we all must strive to move up the value chain. The good news is that these emerging technologies are finally beginning to provide a platform for autonomous learning. To see how this is true for JPA and Hibernate, let's look at how to build a program with this interesting technology.

JPA Shopping List

You need just a few items to get up and running with JPA:

  • One or more entity classes
  • EntityManager instance
  • EntityTransaction instance
  • Persistence unit written in XML
  • Database engine

Once you have these bits and pieces organized, you're ready to start implementing your persistence solution. Think it sounds difficult? Well, it's not! Let's get started with the entity class.

What Is an Entity?

The best way to think of an entity is as something that can be described as a noun in your problem domain; for example, Customer, User, System, Network, and so on. Entities are the low-level atoms that represent your problem domain. You model these atoms as entities. Listing 1 shows a complete entity class called Message.java. For the moment, ignore the lines that start with the "at" (@) symbol; those are called annotations, and I'll discuss them shortly.

Listing 1 An entity class.

import javax.persistence.*;

 

@Entity

@Table(name = "MESSAGES")

public class Message {

  @Id @GeneratedValue

  @Column(name = "MESSAGE_ID")

  private Long id;

 

  @Column(name = "MESSAGE_TEXT")

  private String text;

 

  @ManyToOne(cascade = CascadeType.ALL)

  @JoinColumn(name = "NEXT_MESSAGE_ID")

  private Message nextMessage;

 

  Message() {}

 

  public Message(String text) {

    this.text = text;

  }

  public Long getId() {

    return id;

  }

  private void setId(Long id) {

    this.id = id;

  }

  public String getText() {

    return text;

  }

  public void setText(String text) {

    this.text = text;

  }

  public Message getNextMessage() {

     return nextMessage;

  }

  public void setNextMessage(Message nextMessage) {

    this.nextMessage = nextMessage;

  }

}

Putting aside the annotations for the moment, Listing 1 is just a normal Java class. In fact, the expression used to describe such a class is "Plain Old Java" class, or POJO for short. The POJO in Listing 1 contains three data members (fields), as shown in Listing 2.

Listing 2 The POJO fields from Listing 1.

private Long id;

private String text;

private Message nextMessage;

Looking at the rest of Listing 1, the code is simply made up of two constructors and getter and setter methods for the fields in Listing 2. Now let's examine the annotations.

Getting into Annotations

For the most part, annotations are fairly obvious, and an intelligent guess will generally uncover their purpose in the code. In Listing 1, the annotations start off this way:

@Entity

@Table(name = "MESSAGES")

public class Message

The @Entity annotation identifies the class as being persistent. This is an indication to the entity manager that instances of this class will be written to a database. In other words, each new instance of the class will become a row in a relational database table. But what table will be used to store the instances of Message? To answer this question, look at the second annotation:

@Table(name = "MESSAGES")

As I said, it's generally possible to guess the function of the annotation. This is because annotations are supposed to be relatively self-documenting. In this case, you might correctly guess that instances of the Message class will be stored in a table called MESSAGES. So far, so good—nothing too difficult. Now what about the class code? The next annotations are inside the Message class and look like this:

@Id @GeneratedValue

@Column(name = "MESSAGE_ID")

private Long id;

To understand these annotations, we must take a small detour into database land and consider the issue of table keys.

One of the principal requirements of using databases is uniqueness of table rows. This requirement isn't hard to understand: If you reserve a hotel room, you want your reservation to be unique in the database. This simply means that you (and only you) have a reservation on a given room. Uniqueness can be solved by the use of a key value. Keys can be simple integers, and their only stipulation is that any given key cannot be repeated in any other rows in the table.

Returning to the example of a hotel room reservation, the key can be an integer value, and in the annotation above this is exactly the case. Our annotation uses a private Long member to model the key value. Looking at the complete annotation, you might now be able to guess its overall function:

@Id @GeneratedValue

@Column(name = "MESSAGE_ID")

private Long id;

The @Id annotation indicates that the field following the annotation is an identity or key value. Such values uniquely differentiate the rows in the table, but which column is referenced? The @Column annotation indicates that the key column is to be called MESSAGE_ID.

Let's recap. Our database table is called MESSAGES and it contains a key column called MESSAGE_ID. To complete the picture, let's look at how to map a non-key table column using the following annotation:

@Column(name = "MESSAGE_TEXT")

private String text;

Again, if you apply a logical approach to the annotations you can usually guess what's going on. In this case, we have a new column called MESSAGE_TEXT. This column is also added to the table MESSAGES.

The process of writing a POJO with annotations is often referred to as mapping. In fact, I've often used this term in my consultancy work to describe the process to clients. Most managers can understand your Java coding, and the same is true for SQL coding. JPA work is more difficult for a non-technical person to understand, so a generic term like "mapping" is very useful. In effect, you're mapping between two domains: Java and relational databases. When we run the code you'll see how the mapping works, but for the moment just note that we're bridging two very different technologies—the old and the new!

Returning to the POJO, the next annotation is a little more complex.

@ManyToOne(cascade = CascadeType.ALL)

@JoinColumn(name = "NEXT_MESSAGE_ID")

private Message nextMessage;

Starting at the bottom of this code section, each instance of the Message class contains an instance of Message. Remember that the database contains a row for each instance of Message. The annotation above indicates a relationship between the rows in this table; in this case, the relationship is many-to-one. In other words, one entity has a relationship with many entities. How are these entities related? The relationship is modeled using a column in the table, more specifically one with the @JoinColumn annotation. The latter specifies a column name that forms the basis for the relationship.

The cascade simply relates to the way changes to the owning entity are propagated through the relationship hierarchy.

Other Details of the Entity Class

The remainder of the entity class can be summed up as follows:

  • Default constructor
  • Non-default constructor
  • Methods to set data members
  • Methods to get data members

The default constructor is mandatory for persistent classes. The non-default constructor is used to pre-populate an instance of the entity class. The getters and setters are merely used to retrieve and modify the data members. Now, how do we instantiate the entity class? Simple—we use another class.

Main Program

We need to create an environment in which instances of the entity class can be created. Listing 3 shows a complete class that builds the environment and instantiates and persists our entities.

Listing 3 A client program.

import java.util.*;

import javax.persistence.*;

 

public class HelloWorld {

 

  public static void main(String[] args) {

 

    // Start EntityManagerFactory

    EntityManagerFactory emf =

        Persistence.createEntityManagerFactory("helloworld");

 

    // First unit of work

    EntityManager em = emf.createEntityManager();

    EntityTransaction tx = em.getTransaction();

    tx.begin();

 

    Message message = new Message("Hello World with JPA");

    em.persist(message);

 

    tx.commit();

    em.close();

 

    // Second unit of work

    EntityManager newEm = emf.createEntityManager();

    EntityTransaction newTx = newEm.getTransaction();

    newTx.begin();

 

    List messages =

      newEm.createQuery("select m from Message m order by m.text asc").getResultList();

 

    System.out.println( messages.size() + " message(s) found:" );

 

    for (Object m : messages) {

      Message loadedMsg = (Message) m;

      System.out.println(loadedMsg.getText());

    }

 

    newTx.commit();

    newEm.close();

 

    // Shutting down the application

    emf.close();

  }

}

Listing 3 is divided into "units of work"—transactions in which instances of the entity are created and persisted into the database. Once persisted, the instances are retrieved and displayed to the console.

Installation

The only real installation in the "Hello World" code is unpacking the zip file into a folder. As is often the case with Java code, you should keep the target folder path short and avoid any spaces. On my system, I unpacked the zip file into a folder called C:/java, which resulted in the following directory structure:

C:/java/jpwh-gettingstarted-070401/helloworld-jpa

To run the code, you simply open a DOS console and change to the above directory (described next).

Running the Code

The easiest way to run the code is to open three DOS consoles in the directory containing the Ant build file (called build.xml). To view the Ant build targets, enter the following command:

ant –p

You should see something like Listing 4.

Listing 4 Viewing the Ant targets.

C:/java/jpwh-gettingstarted-070401/helloworld-jpa>ant -p

Buildfile: build.xml

 

Main targets:

 

 clean     Clean the build directory

 dbmanager   Start HSQL DB manager

 run      Build and run HelloWorld

 schemaexport Exports a generated schema to DB and file

 startdb    Run HSQL database server with clean DB

Default target: compile

The following three sections describe how to do the following:

1.       Start the database manager.

2.       Export the schema to the database.

3.       Run the client program.

The easiest way to perform these tasks is to run the appropriate command from a separate DOS console. In other words, open each DOS console in the following folder:

C:/java/jpwh-gettingstarted-070401/helloworld-jpa

Then execute the appropriate Ant target as described in the following sections.

Starting the Database Manager

You'll be glad to hear that to run the code, you don't have to spend ages setting up a database manager. The example program uses an in-memory database called HSQLDB. Of course, you can change the configuration and use any database you like, but the example includes HSQLDB by default. To start the database manager, type the command illustrated in Listing 5.

Listing 5 Running the database engine.

C:/java/jpwh-gettingstarted-070401/helloworld-jpa>ant startdb

Buildfile: build.xml

 

startdb:

  [delete] Deleting directory C:/java/jpwh-gettingstarted-070401/helloworld-jpa/database

   [java] [Server@1d58aae]: [Thread[main,5,main]]: checkRunning(false) entered

   [java] [Server@1d58aae]: [Thread[main,5,main]]: checkRunning(false) exited

   [java] [Server@1d58aae]: Startup sequence initiated from main() method

   [java] [Server@1d58aae]: Loaded properties from [C:/java/jpwh-gettingstarted-070401/helloworld-jpa/server.properties]

   [java] [Server@1d58aae]: Initiating startup sequence...

   [java] [Server@1d58aae]: Server socket opened successfully in 15 ms.

   [java] [Server@1d58aae]: Database [index=0, id=0, db=file:database/db, alias=] opened sucessfully in 375 ms.

   [java] [Server@1d58aae]: Startup sequence completed in 390 ms.

   [java] [Server@1d58aae]: 2008-12-04 11:17:19.187 HSQLDB server 1.8.0 is online

   [java] [Server@1d58aae]: To close normally, connect and execute SHUTDOWN SQL

   [java] [Server@1d58aae]: From command line, use [Ctrl]+[C] to abort abruptly

When you see something like the results shown in Listing 5, you should be ready to export the schema to the database.

Exporting the Schema to the Database

To populate the database, you must create a schema file, which is nothing more than a file containing SQL code to create and configure your tables. The schema can be created and exported all at once with the command in Listing 6.

Listing 6 Exporting the schema.

C:/java/jpwh-gettingstarted-070401/helloworld-jpa>ant schemaexport

Buildfile: build.xml

 

compile:

 

copymetafiles:

 

schemaexport:

[hibernatetool] Executing Hibernate Tool with a JPA Configuration

[hibernatetool] 1. task: hbm2ddl (Generates database schema)

[hibernatetool]

[hibernatetool]   alter table MESSAGES

[hibernatetool]     drop constraint FK131AF14C3CD7F3EA;

[hibernatetool]

[hibernatetool]   drop table MESSAGES if exists;

[hibernatetool]

[hibernatetool]   create table MESSAGES (

[hibernatetool]     MESSAGE_ID bigint generated by default as identity (start with 1),

[hibernatetool]     MESSAGE_TEXT varchar(255),

[hibernatetool]     NEXT_MESSAGE_ID bigint,

[hibernatetool]     primary key (MESSAGE_ID)

[hibernatetool]   );

[hibernatetool]

[hibernatetool]   alter table MESSAGES

[hibernatetool]     add constraint FK131AF14C3CD7F3EA

[hibernatetool]     foreign key (NEXT_MESSAGE_ID)

[hibernatetool]     references MESSAGES;

[hibernatetool] 1 errors occurred while performing <hbm2ddl>.

[hibernatetool] Error #1: java.sql.SQLException: Table not found: MESSAGES in statement [alter table MESSAGES]

 

BUILD SUCCESSFUL

All that happens in Listing 6 is the creation of a schema file and exporting that file into the database engine. If you want to see the schema file, look in the home folder for a file called helloworld-jpa-ddl.sql. The contents of this file are shown in Listing 7.

Listing 7 The auto-generated schema.

alter table MESSAGES

  drop constraint FK131AF14C3CD7F3EA;

 

drop table MESSAGES if exists;

 

create table MESSAGES (

  MESSAGE_ID bigint generated by default as identity (start with 1),

  MESSAGE_TEXT varchar(255),

  NEXT_MESSAGE_ID bigint,

  primary key (MESSAGE_ID)

);

 

alter table MESSAGES

  add constraint FK131AF14C3CD7F3EA

  foreign key (NEXT_MESSAGE_ID)

  references MESSAGES;

Listing 7 demonstrates some of the magic of JPA. This SQL code was generated based on the annotated Java code. You can see how JPA transparently bridges the Java and relational database worlds.

WARNING

Exporting this schema into a live database is decidedly not a good idea! This is particularly the case for production environments. It's done here just to illustrate the power of a JPA solution. I should add that with power comes responsibility, so you must use this knowledge judiciously. The last thing you want to do is destroy a production database.

The error message at the end of Listing 6 merely reflects the fact that the MESSAGES table didn't exist prior to running the script. So, if all has gone well, you should now have a fully created (though not yet populated) database. Let's examine this database.

Starting the Database Manager

The HSQLDB product includes a useful GUI-driven database manager. You can run this manager with the command in Listing 8.

Listing 8 Running the database manager.

C:/java/jpwh-gettingstarted-070401/helloworld-jpa>ant dbmanager

Buildfile: build.xml

 

dbmanager:

   [java] Failed to load preferences. Proceeding with defaults:

   [java]

Don't worry about the diagnostic message in Listing 8—it relates to a missing configuration file. The important point is that you should see a new window similar to the one in Figure 1.

Figure 1 The HSQL Database Manager GUI.

I've executed a SQL query in Figure 1 to illustrate that the database contains no data. It's easy to run SQL with the GUI—just select Command > Select. This loads the text SELECT * FROM and you just append the name of the table—in this example, the table MESSAGES. Once the command is typed, click the Execute SQL button. At this point, you should see something like that illustrated in the lower-right section of Figure 1. The problem is that there's no data! Let's remedy this situation.

Populating the Database

To populate the database, we need to execute the main program, as illustrated in Listing 9.

Listing 9 Running the database—populating the data.

C:/java/jpwh-gettingstarted-070401/helloworld-jpa>ant run

Buildfile: build.xml

 

compile:

 

copymetafiles:

 

run:

   [java] 1 message(s) found:

   [java] Hello World with JPA

 

BUILD SUCCESSFUL

If you compare the program output in Listing 9 with the Java code in Listing 3, you'll see that the following Java code instantiates Message by executing the non-default constructor:

Message message = new Message("Hello World with JPA");

em.persist(message);

The object message is then written (persisted) to the database.

What does the database look like after running the code? Figure 2 illustrates the database contents.

Figure 2 The populated database.

Notice in Figure 2 that the row has been populated with the instance of Message. More of that JPA magic! Notice also that the column NEXT_MESSAGE_ID is blank, because no code was executed to populate the column. However, this is easily fixed by a call to the method setNextMessage().

You now have a great framework with which to explore JPA and persistence.

Conclusion

Getting up and running with JPA need not be too difficult; you follow some fairly straightforward steps. Even the annotations are (for the most part) reasonably comprehensible. It's worth noting that not all organizations are sold on the idea of Java annotations, however. Many organizations prefer to use the tried-and-true approach of external XML files.

Java annotations are pretty much on the leading edge of the language. My own feeling is that annotations are very powerful, but really close to the code! In fact, annotations are part of the code. While the community is increasingly moving in the direction of annotations, this is perhaps somewhat at odds with the notion of decoupled solutions. I don't doubt that annotations will be adopted aggressively across the industry. In fact, I've even worked on projects that have used annotations to produce impressive systems fairly rapidly. The one issue I have with annotations is that they can result in problems that are very difficult to solve; for instance, transaction propagation.

In this article, you've seen how to build an entity class and how to instantiate and manipulate instances of that entity. Setting up the necessary database infrastructure is also not too difficult—at least, for getting a feel for the area with HSQLDB.

I hope that I've at least demystified the area of Java persistence and provided a starting point for delving more deeply into this important topic.


© 2009 Pearson Education, Inc. Informit. All rights reserved.

800 East 96th Street Indianapolis, Indiana 46240

 

 

原创粉丝点击