Implementing composite keys with JPA and Hibernate

The issue of the legacy database schema

Nowadays, with the widespread use and deployment of Object-Relational Mapping (ORM) tools, you don't generally have to think too hard about such arcane issues as composite keys. Normally, the choice of key design can be a simple integer, and this can be left with confidence to the tooling. Occasionally, you come across a situation where a composite key is required, and you need a strategy for this. This tip shows you how to implement composite keys with JPA and Hibernate.

Share:

Stephen B. Morris, CTO, Omey Communications

Stephen Morris is the CTO of Omey Communications in Ireland. For the past 20 years, he has worked for some of the world's largest networking companies on a wide range of software projects, including J2EE/J2SE-based network management systems, billing applications, porting and developing SNMP entities, network device technologies, and GSM mobile networking applications. He is the author of Network Management, MIBs and MPLS: Principles, Design and Implementation (Prentice Hall PTR, 2003) as well as several articles on network management and other topics for InformIT and OnJava.com.


developerWorks Contributing author
        level

25 August 2009

Also available in Russian

The problem definition

This tip starts with a simple description of the problem: defining a composite database key. This is a key that combines a number of columns to uniquely define the rows of a database table. Sometimes, composite keys are called natural keys or business keys. Composite keys are sometimes used because the choice of key relates in some way to the end-user business domain. To define a composite key, you simply take some attributes from the domain and combine them to provide the required degree of row uniqueness. The downside to composite keys is that they are a little difficult to design and code. Also, they tend to tie your database and ORM design to the original domain. The latter may or may not be a big issue.


Entity code

Listing 1 illustrates a Java class called BillingAddress. This class models the billing address for a person or an organization. The billing itself relates to another Java class called PurchaseOrder. There's nothing too amazing here — a purchase order is placed and a billing process ensues.

Listing 1. The BillingAddress class
import javax.persistence.*;
import java.io.Serializable;

@Embeddable
public class BillingAddress implements Serializable {

    private String street;
    private String city;

    BillingAddress() {}

    public BillingAddress(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    private void setStreet(String street) {
        this.street = street;
    }

    public String getCity() {
        return city;
    }

    private void setCity(String city) {
        this.city = city;
    }
}

An important point to note is that the class implements the Java Serializable interface. Notice also the line with the annotation @Embeddable. This annotation is the first piece of the composite key jigsaw puzzle. A Java class with the @Embeddable annotation can itself become a subcomponent of another class. That sounds a lot more complicated than it is. To illustrate, Listing 2 shows the PurchaseOrder class, which uses the BillingAddress class from Listing 1.

Listing 2. The PurchaseOrder class
import javax.persistence.*;

@Entity
@Table(name = "PURCHASE_ORDERS")
@IdClass(BillingAddress.class)
public class PurchaseOrder {

	PurchaseOrder() {}

	PurchaseOrder(BillingAddress billingAddress) {
	street = billingAddress.getStreet();
	city = billingAddress.getCity();
	}

	@Id
	@AttributeOverrides({
	@AttributeOverride(name = "street",
	column = @Column(name="STREET")),
	@AttributeOverride(name = "city",
	column = @Column(name="CITY"))
	})

	private String street;
	private String city;
	private String itemName;

	public String getItemName() {
	return itemName;
	}

	public void setItemName(String itemName) {
	this.itemName = itemName;
	}
}

I always find annotations a bit hard to follow, and Listing 2 is no exception, so I'll break it down into manageable chunks. The first annotation is @Entity, which indicates that the class is a database entity (that is, it will form part of the ORM solution). Typically, when you see the @Entity annotation, you can expect to see a corresponding database table. The latter is indicated by the next annotation — namely, @Table. I find when you break down the process in this way, it becomes easier to understand.

Next up in Listing 2 is the @IdClass annotation, which defines the composite key class reference. You may have noted that this annotation refers to the BillingAddress class from Listing 1. Skipping past the constructors in Listing 2, notice the @Id annotation. This is where the composite key is defined with nested @AttributeOverrides annotations. These annotations are used to define the composite key columns: "STREET" and "CITY" respectively.

Just after the annotations in Listing 2, can you spot two lines of duplicate code from Listing 1? The duplicate code is, of course, the two private data members for street and city. This duplication is required to create the composite key.


The database schema

So far, it's all been fairly technical. Now see this expressed in a more concrete fashion by generating a database schema. Listing 3 illustrates the schema from this ultra-simple ORM database design.

Listing 3. The database schema
drop table PURCHASE_ORDERS if exists;

create table PURCHASE_ORDERS (
        street varchar(255) not null,
        city varchar(255) not null,
        itemName varchar(255),
        primary key (street, city)
);

You can see that the primary key is indeed a composite made up of the street and city fields. What does this look like in a real database — for example, one with a graphical user interface (GUI) tool? Before answering that, I write some simple client code to persist one or two entities to a database.

Listing 4 illustrates an excerpt of some code to instantiate objects of the classes defined earlier.

Listing 4. Some ORM client code
        // Start EntityManagerFactory
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("helloworld");

        // First unit of work
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        PurchaseOrder purchaseOrder = 
          new PurchaseOrder(new BillingAddress("Broad Street", "Boston"));
        purchaseOrder.setItemName("My new computer");
        em.persist(purchaseOrder);

        tx.commit();
        em.close();

The code in Listing 4 illustrates the journey from PurchaseOrder object instantiation and setup all the way through to persistence of this object in the database. This really illustrates the magic that is ORM. Have a look at what happens.

To begin with, an instance of EntityManagerFactory is created, and this is, in turn, used to create an instance of EntityManager called em. The latter is then used to write the PurchaseOrder object instance to the database. The actual persistence into the database occurs as part of a transaction.

A transaction is a set of atomic actions that either all occur successfully or are rolled back in the case of error. As you can see from Listing 4, the EntityManager object is used to create an instance of EntityTransaction called tx. It is the latter object that wraps the unit of work in a transaction.

Notice the calls to persist() and commit(). It's important to remember that no changes to the database occur unless both of these invocations occur. This is the simple pattern of the Java Persistence API (JPA).

To complete the ORM tour, Figure 1 shows what the database looks like after running the code in Listing 4. The code was tested using an in-memory database called HSQLDB, and this product includes a simple GUI tool. The state of the HSQLDB database is shown in Figure 1. You can see that I ran an SQL query on the PURCHASE_ORDERS table. This table was created from the schema, which itself was created from the Java code in the earlier listings.

Figure 1. The populated database
The populated database

In Figure 1, you can see the effect that this line of code from Listing 4 had: purchaseOrder.setItemName("My new computer"). The invocation of the setter code populated the associated column in the database row with the String data: "My new computer." In terms of workflow, you can think of the overall program run as the creation of a purchase order for a new computer followed by a billing process. All the steps in the workflow are implicitly stored in the database.


Concluding comments about composite keys

The composite key defined in listings 1 and 2 allows you to bundle a number of columns together. The combination of columns then provides the required uniqueness so you can have an arbitrary number of rows in the database table. You've seen how it's done. Now, you need to briefly understand why one might take this quirky approach to database design.

Probably the most common reason for using composite keys is for backwards-compatibility. In other words, it occurs in those cases where you need to integrate new database code into a legacy environment. I think it's quite unusual to intentionally design a database this way nowadays, so it's likely you'll only need to create composite keys where it is a long-established practice.

In such cases, my experience is that it's pointless arguing against this approach. If composite keys are the standard, then that's not likely to change any time soon. In many cases, there may be masses of existing data already structured in this way. So, if a change is made from composite keys to numerical keys, then the legacy data has to be migrated. Also, there may be business processes that map onto the composite key data. The combination of all these factors may make composite key design a necessity.

Resources

Learn

  • To listen to interesting interviews and discussions for software developers, check out developerWorks podcasts.
  • Stay current with developerWorks' Technical events and webcasts.
  • Follow developerWorks on Twitter.
  • Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
  • Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
  • Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.

Get products and technologies

  • Visit Sun's JPA wiki to learn more about the Java Persistence API for persistence and object/relational mapping for the Java EE platform.
  • Go to the Hibernate site for background reading, code downloads, and more.
  • Innovate your next open source development project with IBM trial software, available for download or on DVD.
  • Download IBM product evaluation versions or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=421931
ArticleTitle=Implementing composite keys with JPA and Hibernate
publish-date=08252009