Chapter 3. Domain models and metadata

In this chapter

  • Discovering the CaveatEmptor example application
  • Implementing the domain model
  • Object/relational mapping metadata options

The “Hello World” example in the previous chapter introduced you to Hibernate; certainly, it isn’t useful for understanding the requirements of real-world applications with complex data models. For the rest of the book, we use a much more sophisticated example application—CaveatEmptor, an online auction system—to demonstrate Hibernate and Java Persistence. (Caveat emptor means “Let the buyer beware”.)

Major new features in JPA 2

  • A JPA persistence provider now integrates automatically with a Bean Validation provider. When data is stored, the provider automatically validates constraints on persistent classes.
  • The Metamodel API has been added. You can obtain (unfortunately not change) the names, properties, and mapping metadata of the classes in a persistence unit.

We’ll start our discussion of the application by introducing a layered application architecture. Then, you’ll learn how to identify the business entities of a problem domain. You’ll create a conceptual model of these entities and their attributes, called a domain model, and you’ll implement it in Java by creating persistent classes. We’ll spend some time exploring exactly what these Java classes should look like and where they fit within a typical layered application architecture. We’ll also look at the persistence capabilities of the classes and how this aspect influences the design and implementation. We’ll add Bean Validation, which helps to automatically verify the integrity of the domain model data not only for persistent information but all business logic.

We’ll then explore mapping metadata options—the ways you tell Hibernate how your persistent classes and their properties relate to database tables and columns. This can be as simple as adding annotations directly in the Java source code of the classes or writing XML documents that you eventually deploy along with the compiled Java classes that Hibernate accesses at runtime. After reading this chapter, you’ll know how to design the persistent parts of your domain model in complex real-world projects, and what mapping metadata option you’ll primarily prefer and use. Let’s start with the example application.

3.1. The example CaveatEmptor application

The CaveatEmptor example is an online auction application that demonstrates ORM techniques and Hibernate functionality. You can download the source code for the application from www.jpwh.org. We won’t pay much attention to the user interface in this book (it could be web based or a rich client); we’ll concentrate instead on the data access code. When a design decision about data access code that has consequences for the user interface has to be made, we’ll naturally consider both.

In order to understand the design issues involved in ORM, let’s pretend the Caveat-Emptor application doesn’t yet exist and that you’re building it from scratch. Let’s start by looking at the architecture.

3.1.1. A layered architecture

With any nontrivial application, it usually makes sense to organize classes by concern. Persistence is one concern; others include presentation, workflow, and business logic. A typical object-oriented architecture includes layers of code that represent the concerns.

Cross-cutting concerns

There are also so-called cross-cutting concerns, which may be implemented generically—by framework code, for example. Typical cross-cutting concerns include logging, authorization, and transaction demarcation.

A layered architecture defines interfaces between code that implements the various concerns, allowing changes to be made to the way one concern is implemented without significant disruption to code in the other layers. Layering determines the kinds of inter-layer dependencies that occur. The rules are as follows:

  • Layers communicate from top to bottom. A layer is dependent only on the interface of the layer directly below it.
  • Each layer is unaware of any other layers except for the layer just below it.

Different systems group concerns differently, so they define different layers. The typical, proven, high-level application architecture uses three layers: one each for presentation, business logic, and persistence, as shown in figure 3.1.

Figure 3.1. A persistence layer is the basis in a layered architecture.

  • Presentation layer— The user interface logic is topmost. Code responsible for the presentation and control of page and screen navigation is in the presentation layer. The user interface code may directly access business entities of the shared domain model and render them on the screen, along with controls to execute actions. In some architectures, business entity instances might not be directly accessible by user interface code: for example, if the presentation layer isn’t running on the same machine as the rest of the system. In such cases, the presentation layer may require its own special data-transfer model, representing only a transmittable subset of the domain model.
  • Business layer— The exact form of the next layer varies widely between applications. It’s generally agreed that the business layer is responsible for implementing any business rules or system requirements that would be understood by users as part of the problem domain. This layer usually includes some kind of controlling component—code that knows when to invoke which business rule. In some systems, this layer has its own internal representation of the business domain entities. Alternatively, it relies on a domain model implementation, shared with the other layers of the application.
  • Persistence layer— The persistence layer is a group of classes and components responsible for storing data to, and retrieving it from, one or more data stores. This layer needs a model of the business domain entities for which you’d like to keep persistent state. The persistence layer is where the bulk of JPA and Hibernate use takes place.
  • Database— The database is usually external, shared by many applications. It’s the actual, persistent representation of the system state. If an SQL database is used, the database includes a schema and possibly stored procedures for execution of business logic close to the data.
  • Helper and utility classes— Every application has a set of infrastructural helper or utility classes that are used in every layer of the application (such as Exception classes for error handling). These shared infrastructural elements don’t form a layer because they don’t obey the rules for inter-layer dependency in a layered architecture.

Now that you have a high-level architecture, you can focus on the business problem.

3.1.2. Analyzing the business domain

At this stage, you, with the help of domain experts, analyze the business problems your software system needs to solve, identifying the relevant main entities and their interactions. The motivating goal behind the analysis and design of a domain model is to capture the essence of the business information for the application’s purpose.

Entities are usually notions understood by users of the system: payment, customer, order, item, bid, and so forth. Some entities may be abstractions of less concrete things the user thinks about, such as a pricing algorithm, but even these are usually understandable to the user. You can find all these entities in the conceptual view of the business, sometimes called a business model.

From this business model, engineers and architects of object-oriented software create an object-oriented model, still at the conceptual level (no Java code). This model may be as simple as a mental image existing only in the mind of the developer, or it may be as elaborate as a UML class diagram. Figure 3.2 shows a simple model expressed in UML.

Figure 3.2. A class diagram of a typical online auction model

This model contains entities that you’re bound to find in any typical e-commerce system: category, item, and user. This model of the problem domain represents all the entities and their relationships (and perhaps their attributes). We call this kind of object-oriented model of entities from the problem domain, encompassing only those entities that are of interest to the user, a domain model. It’s an abstract view of the real world.

Instead of an object-oriented model, engineers and architects may start the application design with a data model (possibly expressed with an entity-relationship diagram). We usually say that, with regard to persistence, there is little difference between the two; they’re merely different starting points. In the end, what modeling language you use is secondary; we’re most interested in the structure and relationships of the business entities. We care about the rules that have to be applied to guarantee the integrity of data (for example, the multiplicity of relationships) and the code procedures used to manipulate the data.

In the next section, we complete our analysis of the CaveatEmptor problem domain. The resulting domain model will be the central theme of this book.

3.1.3. The CaveatEmptor domain model

The CaveatEmptor site auctions many different kinds of items, from electronic equipment to airline tickets. Auctions proceed according to the English auction strategy: users continue to place bids on an item until the bid period for that item expires, and the highest bidder wins.

In any store, goods are categorized by type and grouped with similar goods into sections and onto shelves. The auction catalog requires some kind of hierarchy of item categories so that a buyer can browse these categories or arbitrarily search by category and item attributes. Lists of items appear in the category browser and search result screens. Selecting an item from a list takes the buyer to an item-detail view where an item may have images attached to it.

An auction consists of a sequence of bids, and one is the winning bid. User details include name, address, and billing information.

The result of this analysis, the high-level overview of the domain model, is shown in figure 3.3. Let’s briefly discuss some interesting features of this model.

Figure 3.3. Persistent classes of the CaveatEmptor domain model and their relationships

Each item can be auctioned only once, so you don’t need to make Item distinct from any auction entities. Instead, you have a single auction item entity named Item. Thus, Bid is associated directly with Item. You model the Address information of a User as a separate class, a User may have three addresses, for home, billing, and shipping. You do allow the user to have many BillingDetails. Subclasses of an abstract class represent the various billing strategies (allowing future extension).

The application may nest a Category inside another Category, and so on. A recursive association, from the Category entity to itself, expresses this relationship. Note that a single Category may have multiple child categories but at most one parent. Each Item belongs to at least one Category.

This representation isn’t the complete domain model but only classes for which you need persistence capabilities. You’d like to store and load instances of Category, Item, User, and so on. We have simplified this high-level overview a little; we may introduce additional classes later or make minor modifications to them when needed for more complex examples.

Certainly, the entities in a domain model should encapsulate state and behavior. For example, the User entity should define the name and address of a customer and the logic required to calculate the shipping costs for items (to this particular customer).

There might be other classes in the domain model that have only transient runtime instances. Consider a WinningBidStrategy class encapsulating the fact that the highest bidder wins an auction. This might be called by the business layer (controller) code when checking the state of an auction. At some point, you might have to figure out how tax for sold items is calculated or how the system may approve a new user account. We don’t consider such business rules or domain model behavior to be unimportant; rather, this concern is mostly orthogonal to the problem of persistence.

Now that you have a (rudimentary) application design with a domain model, the next step is to implement it in Java.

ORM without a domain model

Object persistence with full ORM is most suitable for applications based on a rich domain model. If your application doesn’t implement complex business rules or complex interactions between entities (or if you have few entities), you may not need a domain model. Many simple and some not-so-simple problems are perfectly suited to table-oriented solutions, where the application is designed around the database data model instead of around an object-oriented domain model, often with logic executed in the database (stored procedures). Another aspect to consider is the learning curve: once you’re proficient with Hibernate, you’ll use it for all applications, even as a simple SQL query generator and result mapper. If you’re just learning ORM, a trivial use case may not justify your invested time and overhead.

3.2. Implementing the domain model

You’ll start with an issue that any implementation must deal with: the separation of concerns. The domain model implementation is usually a central, organizing component; it’s reused heavily whenever you implement new application functionality. For this reason, you should be prepared to go to some lengths to ensure that concerns other than business aspects don’t leak into the domain model implementation.

3.2.1. Addressing leakage of concerns

When concerns such as persistence, transaction management, or authorization start to appear in the domain model classes, this is an example of leakage of concerns. The domain model implementation is such an important piece of code that it shouldn’t depend on orthogonal Java APIs. For example, code in the domain model shouldn’t perform JNDI lookups or call the database via the JDBC API, not directly and not through an intermediate abstraction. This allows you to reuse the domain model classes virtually anywhere:

  • The presentation layer can access instances and attributes of domain model entities when rendering views.
  • The controller components in the business layer can also access the state of domain model entities and call methods of the entities to execute business logic.
  • The persistence layer can load and store instances of domain model entities from and to the database, preserving their state.

Most important, preventing leakage of concerns makes it easy to unit-test the domain model without the need for a particular runtime environment or container, or the need for mocking any service dependencies. You can write unit tests that verify the correct behavior of your domain model classes without any special test harness. (We aren’t talking about testing “load from the database” and “store in the database” aspects, but “calculate the shipping cost and tax” behavior.)

The Java EE standard solves the problem of leaky concerns with metadata, as annotations within your code or externalized as XML descriptors. This approach allows the runtime container to implement some predefined cross-cutting concerns—security, concurrency, persistence, transactions, and remoteness—in a generic way, by intercepting calls to application components.

Hibernate isn’t a Java EE runtime environment, and it’s not an application server. It’s an implementation of just one specification under the Java EE umbrella—JPA—and a solution for just one of these concerns: persistence.

JPA defines the entity class as the primary programming artifact. This programming model enables transparent persistence, and a JPA provider such as Hibernate also offers automated persistence.

3.2.2. Transparent and automated persistence

We use transparent to mean a complete separation of concerns between the persistent classes of the domain model and the persistence layer. The persistent classes are unaware of—and have no dependency on—the persistence mechanism. We use automatic to refer to a persistence solution (your annotated domain, the layer, and mechanism) that relieves you of handling low-level mechanical details, such as writing most SQL statements and working with the JDBC API.

The Item class of the CaveatEmptor domain model, for example, shouldn’t have any runtime dependency on any Java Persistence or Hibernate API. Furthermore:

  • JPA doesn’t require that any special superclasses or interfaces be inherited or implemented by persistent classes. Nor are any special classes used to implement attributes and associations. (Of course, the option to use both techniques is always there.)
  • You can reuse persistent classes outside the context of persistence, in unit tests or in the presentation layer, for example. You can create instances in any runtime environment with the regular Java new operator, preserving testability and reusability.
  • In a system with transparent persistence, instances of entities aren’t aware of the underlying data store; they need not even be aware that they’re being persisted or retrieved. JPA externalizes persistence concerns to a generic persistence manager API.
  • Hence, most of your code, and certainly your complex business logic, doesn’t have to concern itself with the current state of a domain model entity instance in a single thread of execution.

We regard transparency as a requirement because it makes an application easier to build and maintain. Transparent persistence should be one of the primary goals of any ORM solution. Clearly, no automated persistence solution is completely transparent: Every automated persistence layer, including JPA and Hibernate, imposes some requirements on the persistent classes. For example, JPA requires that collection-valued attributes be typed to an interface such as java.util.Set or java.util.List and not to an actual implementation such as java.util.HashSet (this is a good practice anyway). Or, a JPA entity class has to have a special attribute, called the database identifier (which is also less of a restriction but usually convenient).

You now know why the persistence mechanism should have minimal impact on how you implement a domain model, and that transparent and automated persistence are required. Our preferred programming model to archive this is POJO.

Around 10 years ago, many developers started talking about POJO, a back-to-basics approach that essentially revives JavaBeans, a component model for UI development, and reapplies it to the other layers of a system. Several revisions of the EJB and JPA specifications brought us new lightweight entities, and it would be appropriate to call them persistence-capable JavaBeans. Java engineers often use all these terms as synonyms for the same basic design approach.

POJO

POJO is the acronym for Plain Old Java Objects. Martin Fowler, Rebecca Parsons, and Josh Mackenzie coined this term in 2000.

You shouldn’t be too concerned about what terms we use in this book; the ultimate goal is to apply the persistence aspect as transparently as possible to Java classes. Almost any Java class can be persistence-capable if you follow some simple practices. Let’s see how this looks in code.

3.2.3. Writing persistence-capable classes

Working with fine-grained and rich domain models is a major Hibernate objective. This is a reason we work with POJOs. In general, using fine-grained objects means more classes than tables.

A persistence-capable plain-old Java class declares attributes, which represent state, and business methods, which define behavior. Some attributes represent associations to other persistence-capable classes.

A POJO implementation of the User entity of the domain model is shown in the following listing. Let’s walk through the code.

Listing 3.1. POJO implementation of the User class

Path: /model/src/main/java/org/jpwh/model/simple/User.java

public class User implements Serializable {
<enter/>
    protected String username;
<enter/>
    public User() {
    }
<enter/>
    public String getUsername() {
        return username;
    }
<enter/>
    public void setUsername(String username) {
        this.username = username;
    }
<enter/>
    public BigDecimal calcShippingCosts(Address fromLocation) {
        // Empty implementation of business method
        return null;
    }
<enter/>
    // ...
}
<enter/>

JPA doesn’t require that persistent classes implement java.io.Serializable. But when instances are stored in an HttpSession or passed by value using RMI, serialization is necessary. Although this might not occur in your application, the class will be serializable without any additional work, and there are no downsides to declaring that. (We aren’t going to declare it on every example, assuming that you know when it will be necessary.)

The class can be abstract and, if needed, extend a non-persistent class or implement an interface. It must be a top-level class, not nested within another class. The persistence-capable class and any of its methods can’t be final (a requirement of the JPA specification).

Unlike the JavaBeans specification, which requires no specific constructor, Hibernate (and JPA) require a constructor with no arguments for every persistent class. Alternatively, you might not write a constructor at all; Hibernate will then use the Java default constructor. Hibernate calls classes using the Java reflection API on such a no-argument constructor to create instances. The constructor may not be public, but it has to be at least package-visible if Hibernate will use runtime-generated proxies for performance optimization. Also, consider the requirements of other specifications: the EJB standard requires public visibility on session bean constructors, just like the JavaServer Faces (JSF) specification requires for its managed beans. There are other situations when you’d want a public constructor to create an “empty” state: for example, query-by-example building.

The properties of the POJO implement the attributes of the business entities—for example, the username of User. You usually implement properties as private or protected member fields, together with public or protected property accessor methods: for each field a method for retrieving its value and a method for setting the value. These methods are known as the getter and setter, respectively. The example POJO in listing 3.1 declares getter and setter methods for the username property.

The JavaBean specification defines the guidelines for naming accessor methods; this allows generic tools like Hibernate to easily discover and manipulate property values. A getter method name begins with get, followed by the name of the property (the first letter in uppercase); a setter method name begins with set and similarly is followed by the name of the property. You may begin getter methods for Boolean properties with is instead of get.

Hibernate doesn’t require accessor methods. You can choose how the state of an instance of your persistent classes should be persisted. Hibernate will either directly access fields or call accessor methods. Your class design isn’t disturbed much by these considerations. You can make some accessor methods non-public or completely remove them—then configure Hibernate to rely on field access for these properties.

Should property fields and accessor methods be private, protected, or package visible?

Typically, you want to discourage direct access to the internal state of your class, so you don’t make attribute fields public. If you make fields or methods private, you’re effectively declaring that nobody should ever access them; only you’re allowed to do that (or a service like Hibernate). This is a definitive statement. There are often good reasons for someone to access your “private” internals—usually to fix one of your bugs—and you only make people angry if they have to fall back to reflection access in an emergency. Instead, you might assume or know that the engineer who comes after you has access to your code and knows what they’re doing.

The protected visibility then is a more reasonable default. You’re forbidding direct public access, indicating that this particular member detail is internal, but allowing access by subclasses if need be. You trust the engineer who creates the subclass. Package visibility is rude: you’re forcing someone to create code in the same package to access member fields and methods; this is extra work for no good reason. Most important, these recommendations for visibility are relevant for environments without security policies and a runtime SecurityManager. If you have to keep your internal code private, make it private.

Although trivial accessor methods are common, one of the reasons we like to use Java-Beans-style accessor methods is that they provide encapsulation: you can change the hidden internal implementation of an attribute without any changes to the public interface. If you configure Hibernate to access attributes through methods, you abstract the internal data structure of the class—the instance variables—from the design of the database.

For example, if your database stores the name of a user as a single NAME column, but your User class has firstname and lastname fields, you can add the following persistent name property to the class.

Listing 3.2. POJO implementation of the User class with logic in accessor methods
public class User {
<enter/>
    protected String firstname;
    protected String lastname;
<enter/>
    public String getName() {
        return firstname + ' ' + lastname;
    }
<enter/>
    public void setName(String name) {
        StringTokenizer t = new StringTokenizer(name);
        firstname = t.nextToken();
        lastname = t.nextToken();
    }
}

Later, you’ll see that a custom type converter in the persistence service is a better way to handle many of these kinds of situations. It helps to have several options.

Another issue to consider is dirty checking. Hibernate automatically detects state changes in order to synchronize the updated state with the database. It’s usually safe to return a different instance from the getter method than the instance passed by Hibernate to the setter. Hibernate compares them by value—not by object identity—to determine whether the attribute’s persistent state needs to be updated. For example, the following getter method doesn’t result in unnecessary SQL UPDATEs:

There is one important exception to this: collections are compared by identity! For a property mapped as a persistent collection, you should return exactly the same collection instance from the getter method that Hibernate passed to the setter method. If you don’t, Hibernate will update the database, even if no update is necessary, every time the state held in memory is synchronized with the database. You should usually avoid this kind of code in accessor methods:

Of course, this won’t be a problem if Hibernate is accessing the names field directly, ignoring your getter and setter methods.

How does Hibernate handle exceptions when your accessor methods throw them? If Hibernate uses accessor methods when loading and storing instances and a RuntimeException (unchecked) is thrown, the current transaction is rolled back, and the exception is yours to handle in the code that called the Java Persistence (or Hibernate native) API. If you throw a checked application exception, Hibernate wraps the exception into a RuntimeException.

The example in listing 3.2 also defines a business method that calculates the cost of shipping an item to a particular user (we left out the implementation of this method).

Next, we’ll focus on the relationships between entities and associations between persistent classes.

3.2.4. Implementing POJO associations

You’ll now see how to associate and create different kinds of relationships between objects: one-to-many, many-to-one, and bidirectional relationships. We’ll look at the scaffolding code needed to create these associations, how to simplify relationship management, and how to enforce the integrity of these relationships.

You create properties to express associations between classes, and you (typically) call accessor methods to navigate from instance to instance at runtime. Let’s consider the associations defined by the Item and Bid persistent classes, as shown in figure 3.4.

Figure 3.4. Associations between the Item and Bid classes

As with all of our UML class diagrams, we left out the association-related attributes, Item#bids and Bid#item. These properties and the methods that manipulate their values are called scaffolding code. This is what the scaffolding code for the Bid class looks like:

Path: /model/src/main/java/org/jpwh/model/simple/Bid.java

public class Bid {
<enter/>
    protected Item item;
<enter/>
    public Item getItem() {
        return item;
    }
<enter/>
    public void setItem(Item item) {
        this.item = item;
    }
}

The item property allows navigation from a Bid to the related Item. This is an association with many-to-one multiplicity; users can make many bids for each item. Here is the Item class’s scaffolding code:

Path: /model/src/main/java/org/jpwh/model/simple/Item.java

public class Item {
<enter/>
    protected Set<Bid> bids = new HashSet<Bid>();
<enter/>
    public Set<Bid> getBids() {
        return bids;
    }
<enter/>
    public void setBids(Set<Bid> bids) {
        this.bids = bids;
    }
}

This association between the two classes allows bidirectional navigation: the many-to-one is from this perspective a one-to-many multiplicity (again, one item can have many bids). The scaffolding code for the bids property uses a collection interface type, java.util.Set. JPA requires interfaces for collection-typed properties, where you must use java.util.Set, java.util.List, or java.util.Collection rather than HashSet, for example. It’s good practice to program to collection interfaces anyway, rather than concrete implementations, so this restriction shouldn’t bother you.

You choose a Set and initialize the field to a new HashSet because the application disallows duplicate bids. This is good practice because you avoid any NullPointer-Exceptions when someone is accessing the property of a new Item without any bids. The JPA provider is also required to set a non-empty value on any mapped collection-valued property: for example, when an Item without bids is loaded from the database. (It doesn’t have to use a HashSet; the implementation is up to the provider. Hibernate has its own collection implementations with additional capabilities—for example, dirty checking.)

Shouldn’t bids on an item be stored in a list?

The first reaction is often to preserve the order of elements as they’re entered by users, because this may also be the order in which you will show them later. Certainly, in an auction application there has to be some defined order in which the user sees bids for an item—for example, highest bid first or newest bid last. You might even work with a java.util.List in your user interface code to sort and display bids of an item. That doesn’t mean this display order should be durable; data integrity isn’t affected by the order in which bids are displayed. You need to store the amount of each bid, so you can find the highest bid, and you need to store a timestamp for each bid when it’s created, so you can find the newest bid. When in doubt, keep your system flexible and sort the data when it’s retrieved from the datastore (in a query) and/or shown to the user (in Java code), not when it’s stored.

Just like for basic properties, accessor methods for associations need to be declared public only if they’re part of the external interface of the persistent class used by the application logic to create a link between two instances. We’ll now focus on this issue, because managing the link between an Item and a Bid is much more complicated in Java code than it is in an SQL database, with declarative foreign key constraints. In our experience, engineers are often unaware of this complication arising from a network object model with bidirectional references (pointers). Let’s walk through the issue step by step.

The basic procedure for linking a Bid with an Item looks like this:

anItem.getBids().add(aBid);
aBid.setItem(anItem);

Whenever you create this bidirectional link, two actions are required:

  • You must add the Bid to the bids collection of the Item.
  • The item property of the Bid must be set.

JPA doesn’t manage persistent associations. If you want to manipulate an association, you must write exactly the same code you would write without Hibernate. If an association is bidirectional, you must consider both sides of the relationship. If you ever have problems understanding the behavior of associations in JPA, just ask yourself, “What would I do without Hibernate?” Hibernate doesn’t change the regular Java semantics.

We recommend that you add convenience methods that group these operations, allowing reuse and helping ensure correctness, and in the end guaranteeing data integrity (a Bid is required to have a reference to an Item). The next listing shows such a convenience method in the Item class.

Listing 3.3. A convenience method simplifies relationship management

Path: /model/src/main/java/org/jpwh/model/simple/Item.java

The addBid() method not only reduces the lines of code when dealing with Item and Bid instances, but also enforces the cardinality of the association. You avoid errors that arise from leaving out one of the two required actions. You should always provide this kind of grouping of operations for associations, if possible. If you compare this with the relational model of foreign keys in an SQL database, you can easily see how a network and pointer model complicates a simple operation: instead of a declarative constraint, you need procedural code to guarantee data integrity.

Because you want addBid() to be the only externally visible mutator method for the bids of an item (possibly in addition to a removeBid() method), you can make the Item#setBids() method private or drop it and configure Hibernate to directly access fields for persistence. Consider making the Bid#setItem() method package-visible, for the same reason.

The Item#getBids() getter method still returns a modifiable collection, so clients can use it to make changes that aren’t reflected on the inverse side. Bids added directly to the collection wouldn’t have a reference to an item—an inconsistent state, according to your database constraints. To prevent this, you can wrap the internal collection before returning it from the getter method, with Collections.unmodifiable-Collection(c) and Collections.unmodifiableSet(s). The client then gets an exception if it tries to modify the collection; you therefore force every modification to go through the relationship management method that guarantees integrity. Note that in this case you’ll have to configure Hibernate for field access, because the collection returned by the getter method is then not the same as the one given to the setter method.

An alternative strategy is immutable instances. For example, you could enforce integrity by requiring an Item argument in the constructor of Bid, as shown in the following listing.

Listing 3.4. Enforcing integrity of relationships with a constructor

Path: /model/src/main/java/org/jpwh/model/simple/Bid.java

In this constructor, the item field is set; no further modification of the field value should occur. The collection on the “other” side is also updated for a bidirectional relationship. There is no Bid#setItem() method, and you probably shouldn’t expose a public Item#setBids() method.

There are several problems with this approach. First, Hibernate can’t call this constructor. You need to add a no-argument constructor for Hibernate, and it needs to be at least package-visible. Furthermore, because there is no setItem() method, Hibernate would have to be configured to access the item field directly. This means the field can’t be final, so the class isn’t guaranteed to be immutable.

In the examples in this book, we’ll sometimes write scaffolding methods such as the Item#addBid() shown earlier, or we may have additional constructors for required values. It’s up to you how many convenience methods and layers you want to wrap around the persistent association properties and/or fields, but we recommend being consistent and applying the same strategy to all your domain model classes. For the sake of readability, we won’t always show convenience methods, special constructors, and other such scaffolding in future code samples and assume you’ll add them according to your own taste and requirements.

You now have seen domain model classes, how to represent their attributes, and the relationships between them. Next, we’ll increase the level of abstraction, adding metadata to the domain model implementation and declaring aspects such as validation and persistence rules.

3.3. Domain model metadata

Metadata is data about data, so domain model metadata is information about your domain model. For example, when you use the Java reflection API to discover the names of classes of your domain model or the names of their attributes, you’re accessing domain model metadata.

ORM tools also require metadata, to specify the mapping between classes and tables, properties and columns, associations and foreign keys, Java types and SQL types, and so on. This object/relational mapping metadata governs the transformation between the different type systems and relationship representations in object-oriented and SQL systems. JPA has a metadata API, which you can call to obtain details about the persistence aspects of your domain model, such as the names of persistent entities and attributes. First, it’s your job as an engineer to create and maintain this information.

JPA standardizes two metadata options: annotations in Java code and externalized XML descriptor files. Hibernate has some extensions for native functionality, also available as annotations and/or XML descriptors. Usually we prefer either annotations or XML files as the primary source of mapping metadata. After reading this section, you’ll have the background information to make an educated decision for your own project.

We’ll also discuss Bean Validation (JSR 303) and how it provides declarative validation for your domain model (or any other) classes. The reference implementation of this specification is the Hibernate Validator project. Most engineers today prefer Java annotations as the primary mechanism for declaring metadata.

3.3.1. Annotation-based metadata

The big advantage of annotations is to put metadata next to the information it describes, instead of separating it physically into a different file. Here’s an example:

Path: /model/src/main/java/org/jpwh/model/simple/Item.java

import javax.persistence.Entity;
<enter/>
@Entity
public class Item {
<enter/>
}

You can find the standard JPA mapping annotations in the javax.persistence package. This example declares the Item class as a persistent entity using the @javax.persistence.Entity annotation. All of its attributes are now automatically persistent with a default strategy. That means you can load and store instances of Item, and all properties of the class are part of the managed state.

(If you followed the previous chapter, you probably notice the missing required @Id annotation and identifier property. If you want to try the Item example, you’ll have to add an identifier property. We’ll discuss identifier properties again in the next chapter, in section 4.2.)

Annotations are type-safe, and the JPA metadata is included in the compiled class files. Hibernate then reads the classes and metadata with Java reflection when the application starts. The IDE can also easily validate and highlight annotations—they’re regular Java types, after all. If you refactor your code, you rename, delete, or move classes and properties all the time. Most development tools and editors can’t refactor XML element and attribute values, but annotations are part of the Java language and are included in all refactoring operations.

Is my class now dependent on JPA?

Yes, but it’s a compile-time only dependency. You need JPA libraries on your class-path when compiling the source of your domain model class. The Java Persistence API isn’t required on the classpath when you create an instance of the class: for example, in a desktop client application that doesn’t execute any JPA code. Only when you access the annotations through reflection at runtime (as Hibernate does internally when it reads your metadata) will you need the packages on the classpath.

When the standardized Java Persistence annotations are insufficient, a JPA provider may offer additional annotations.

Using vendor extensions

Even if you map most of your application’s model with JPA-compatible annotations from the javax.persistence package, you’ll have to use vendor extensions at some point. For example, some performance-tuning options you’d expect to be available in high-quality persistence software are only available as Hibernate-specific annotations. This is how JPA providers compete, so you can’t avoid annotations from other packages—there’s a reason why you picked Hibernate.

This is the Item entity source code again with a Hibernate-only mapping option:

import javax.persistence.Entity;
<enter/>
@Entity
@org.hibernate.annotations.Cache(
    usage = org.hibernate.annotations.CacheConcurrencyStrategy.READ_WRITE
)
public class Item {
<enter/>
}

We prefer to prefix Hibernate annotations with the full org.hibernate.annotations package name. Consider this good practice, because you can easily see what metadata for this class is from the JPA specification and which is vendor-specific. You can also easily search your source code for “org.hibernate.annotations” and get a complete overview of all nonstandard annotations in your application in a single search result.

If you switch your Java Persistence provider, you only have to replace the vendor-specific extensions where you can expect a similar feature set to be available with most mature JPA implementations. Of course, we hope you’ll never have to do this, and it doesn’t happen often in practice—just be prepared.

Annotations on classes only cover metadata that is applicable to that particular class. You often need metadata at a higher level, for an entire package or even the whole application.

Global annotation metadata

The @Entity annotation maps a particular class. JPA and Hibernate also have annotations for global metadata. For example, a @NamedQuery has global scope; you don’t apply it to a particular class. Where should you place this annotation?

Although it’s possible to place such global annotations in the source file of a class (any class, really, at the top), we’d rather keep global metadata in a separate file. Package-level annotations are a good choice; they’re in a file called package-info.java in a particular package directory. You can see an example of global named query declarations in the following listing.

Listing 3.5. Global metadata in a package-info.java file

Path: /model/src/main/java/org/jpwh/model/querying/package-info.java

Unless you’ve used package-level annotations before, the syntax of this file with the package and import declarations at the bottom is probably new to you.

There is a reason the previous code example only includes annotations from the Hibernate package and no Java Persistence annotations. We ignored the standardized JPA @org.javax.persistence.NamedQuery annotation and used the Hibernate alternative. The JPA annotations don’t have package applicability—we don’t know why. In fact, JPA doesn’t allow annotations in a package-info.java file. The native Hibernate annotations offer the same, and sometimes even more, functionality, so this shouldn’t be too much of a problem. If you don’t want to use the Hibernate annotations, you’ll have to either put the JPA annotations at the top of any class (you could have an otherwise empty MyNamedQueries class as part of your domain model) or use an XML file, as you’ll see later in this section.

Annotations will be our primary tool throughout this book for ORM metadata, and there is much to learn about this subject. Before we look at some alternative mapping styles with XML files, let’s use some simple annotations to improve the domain model classes with validation rules.

3.3.2. Applying Bean Validation rules

Most applications contain a multitude of data-integrity checks. You’ve seen what happens when you violate one of the simplest data-integrity constraints: you get a NullPointerException when you expect a value to be available. Other examples are a string-valued property that shouldn’t be empty (remember, an empty string isn’t null), a string that has to match a particular regular expression pattern, and a number or date value that must be within a certain range.

These business rules affect every layer of an application: The user interface code has to display detailed and localized error messages. The business and persistence layers must check input values received from the client before passing them to the datastore. The SQL database has to be the final validator, ultimately guaranteeing the integrity of durable data.

The idea behind Bean Validation is that declaring rules such as “This property can’t be null” or “This number has to be in the given range” is much easier and less error-prone than writing if-then-else procedures repeatedly. Furthermore, declaring these rules on the central component of your application, the domain model implementation, enables integrity checks in every layer of the system. The rules are then available to the presentation and persistence layers. And if you consider how data-integrity constraints affect not only your Java application code but also your SQL database schema—which is a collection of integrity rules—you might think of Bean Validation constraints as additional ORM metadata.

Look at the following extended Item domain model class.

Listing 3.6. Applying validation constraints on Item entity fields

Path: /model/src/main/java/org/jpwh/model/simple/Item.java

import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
<enter/>
@Entity
public class Item {
<enter/>
   @NotNull
   @Size(
       min = 2,
       max = 255,
       message = "Name is required, maximum 255 characters."
   )
   protected String name;
   @Future
   protected Date auctionEnd;
}

You add two more attributes—the name of an item and the auctionEnd date—when an auction concludes. Both are typical candidates for additional constraints: you want to guarantee that the name is always present and human readable (one-character item names don’t make much sense), but it shouldn’t be too long—your SQL database will be most efficient with variable-length strings up to 255 characters, and your user interface also has some constraints on visible label space. The ending time of an auction obviously should be in the future. If you don’t provide an error message, a default message will be used. Messages can be keys to external properties files, for internationalization.

The validation engine will access the fields directly if you annotate the fields. If you prefer calls through accessor methods, annotate the getter method with validation constraints, not the setter. Then constraints are part of the class’s API and included in its Javadoc, making the domain model implementation easier to understand. Note that this is independent from access by the JPA provider; that is, Hibernate Validator may call accessor methods, whereas Hibernate ORM may call fields directly.

Bean Validation isn’t limited to the built-in annotations; you can create your own constraints and annotations. With a custom constraint, you can even use class-level annotations and validate several attribute values at the same time on an instance of the class. The following test code shows how you can manually check the integrity of an Item instance.

Listing 3.7. Testing an Item instance for constraint violations

Path: /examples/src/test/java/org/jpwh/test/simple/ModelOperations.java

We’re not going to explain this code in detail but offer it for you to explore. You’ll rarely write this kind of validation code; most of the time, this aspect is automatically handled by your user interface and persistence framework. It’s therefore important to look for Bean Validation integration when selecting a UI framework. JSF version 2 and newer automatically integrates with Bean Validation, for example.

Hibernate, as required from any JPA provider, also automatically integrates with Hibernate Validator if the libraries are available on the classpath and offers the following features:

  • You don’t have to manually validate instances before passing them to Hibernate for storage.
  • Hibernate recognizes constraints on persistent domain model classes and triggers validation before database insert or update operations. When validation fails, Hibernate throws a ConstraintViolationException, containing the failure details, to the code calling persistence-management operations.
  • The Hibernate toolset for automatic SQL schema generation understands many constraints and generates SQL DDL-equivalent constraints for you. For example, an @NotNull annotation translates into an SQL NOT NULL constraint, and an @Size(n) rule defines the number of characters in a VARCHAR(n)-typed column.

You can control this behavior of Hibernate with the <validation-mode> element in your persistence.xml configuration file. The default mode is AUTO, so Hibernate will only validate if it finds a Bean Validation provider (such as Hibernate Validator) on the classpath of the running application. With mode CALLBACK, validation will always occur, and you’ll get a deployment error if you forget to bundle a Bean Validation provider. The NONE mode disables automatic validation by the JPA provider.

You’ll see Bean Validation annotations again later in this book; you’ll also find them in the example code bundles. At this point we could write much more about Hibernate Validator, but we’d only repeat what is already available in the project’s excellent reference guide. Have a look, and find out more about features such as validation groups and the metadata API for discovery of constraints.

The Java Persistence and Bean Validation standards embrace annotations aggressively. The expert groups have been aware of the advantages of XML deployment descriptors in certain situations, especially for configuration metadata that changes with each deployment.

3.3.3. Externalizing metadata with XML files

You can replace or override every annotation in JPA with an XML descriptor element. In other words, you don’t have to use annotations if you don’t want to, or if keeping mapping metadata separate from source code is for whatever reason advantageous to your system design.

XML metadata with JPA

The following listing shows a JPA XML descriptor for a particular persistence unit.

Listing 3.8. JPA XML descriptor containing the mapping metadata of a persistence unit

The JPA provider automatically picks up this descriptor if you place it in a META-INF/orm.xml file on the classpath of the persistence unit. If you prefer to use a different name or several files, you’ll have to change the configuration of the persistence unit in your META-INF/persistence.xml file:

Path: /model/src/main/resources/META-INF/persistence.xml

<persistence-unit name="SimpleXMLCompletePU">
    ...

<enter/>
    <mapping-file>simple/Mappings.xml</mapping-file>
    <mapping-file>simple/Queries.xml</mapping-file>
    ...
</persistence-unit>

If you include the <xml-mapping-metadata-complete> element, the JPA provider ignores all annotations on your domain model classes in this persistence unit and relies only on the mappings as defined in the XML descriptor(s). You can (redundantly in this case) enable this on an entity level, with <metadata-complete="true"/>. If enabled, the JPA provider assumes that you mapped all attributes of the entity in XML and that it should ignore all annotations for this particular entity.

Instead, if you don’t want to ignore but override the annotation metadata, don’t mark the XML descriptors as “complete”, and name the class and property to override:

Here you map the name property to the ITEM_NAME column; by default, the property would map to the NAME column. Hibernate will now ignore any existing annotations from the javax.persistence.annotation and org.hibernate.annotations packages on the name property of the Item class. But Hibernate doesn’t ignore Bean Validation annotations and still applies them for automatic validation and schema generation! All other annotations on the Item class are also recognized. Note that you don’t specify an access strategy in this mapping, so field access or accessor methods are used, depending on the position of the @Id annotation in Item. (We’ll get back to this detail in the next chapter.)

We won’t talk much about JPA XML descriptors in this book. The syntax of these documents is a 1:1 mirror of the JPA annotation syntax, so you shouldn’t have any problems writing them. We’ll focus on the important aspect: the mapping strategies. The syntax used to write down metadata is secondary.

Unfortunately, like many other schemas in the Java EE world, the JPA orm_2_0.xsd doesn’t allow vendor extensions. You can’t have elements and attributes from another namespace in the JPA XML mapping documents. Consequently, using vendor extensions and Hibernate native features requires falling back to a different XML syntax.

Hibernate XML mapping files

The native Hibernate XML mapping file format was the original metadata option before JDK 5 introduced annotations. By convention, you name these files with the suffix.hbm.xml. The following listing shows a basic Hibernate XML mapping document.

Listing 3.9. Metadata document in Hibernate’s native XML syntax

Path: /model/src/main/resources/simple/Native.hbm.xml

  1. Metadata is declared ion a <hibernate-mapping> root element. Attributes such as package name and default-access apply to all mappings in this file. You may include as many entity class mappings as you like.

Note that this XML file declares a default XML namespace for all elements; this is a new option in Hibernate 5. If you have existing mapping files for Hibernate 4 or older with XML document type declarations, you can continue using them.

Although it’s possible to declare mappings for multiple classes in one mapping file by using multiple <class> elements, many older Hibernate projects are organized with one mapping file per persistent class. The convention is to give the file the same name and package as the mapped class: for example, my/model/Item.hbm.xml for the my.model.Item class.

A class mapping in a Hibernate XML document is a “complete” mapping; that is, any other mapping metadata for that class, whether in annotations or JPA XML files, will trigger a “duplicate mapping” error on startup. If you map a class in a Hibernate XML file, this declaration has to include all mapping details. You can’t override individual properties or extend an existing mapping. In addition, you have to list and map all persistent properties of an entity class in a Hibernate XML file. If you don’t map a property, Hibernate considers it transient state. Compare this with JPA mappings, where the @Entity annotation alone will make all properties of a class persistent.

Hibernate native XML files are no longer the primary choice for declaring the bulk of a project’s ORM metadata. Most engineers now prefer annotations. Native XML metadata files are mostly used to gain access to special Hibernate features that aren’t available as annotations or are easier to maintain in XML files (for example, because it’s deployment-dependent configuration metadata). You aren’t required to have any <class> elements in a Hibernate XML mapping file. Thus, all metadata in these files can be global to the persistence unit, such as externalized (even native SQL) query strings, custom type definitions, auxiliary SQL DDL for particular DBMS products, dynamic persistence context filters, and so on.

When we later discuss such advanced and native Hibernate features, we’ll show you how to declare them in Hibernate XML files. As already mentioned, your focus should be on understanding the essence of a mapping strategy, and most of our examples will use JPA and Hibernate annotations to express these strategies.

The approaches we’ve described so far assume that all ORM metadata is known at development (or deployment) time. Suppose that some information isn’t known before the application starts. Can you programmatically manipulate the mapping metadata at runtime? We’ve also mentioned the JPA metadata API for access to persistence unit details. How does that work, and when is it useful?

3.3.4. Accessing metadata at runtime

The JPA specification provides programming interfaces for accessing the metamodel of persistent classes. There are two flavors of the API. One is more dynamic in nature and similar to basic Java reflection. The second option is a static metamodel, typically produced by a Java 6 annotation processor. For both options, access is read-only; you can’t modify the metadata at runtime.

Hibernate also offers a native metamodel API that supports read and write access and much more detail about the ORM. We don’t cover this native API (found in org.hibernate.cfg.Configuration) in the book because it was already deprecated, and a replacement API wasn’t available at the time of writing. Please refer to the Hibernate documentation for the latest updates on this feature.

The dynamic Metamodel API in Java Persistence

Sometimes—for example, when you want to write some custom validation or generic UI code—you’d like to get programmatic access to the persistent attributes of an entity. You’d like to know what persistent classes and attributes your domain model has dynamically. The code in the next listing shows how to read metadata with Java Persistence interfaces.

Listing 3.10. Obtaining entity type information with the Metamodel API

Path: /examples/src/test/java/org/jpwh/test/simple/AccessJPAMetamodel.java

Metamodel mm = entityManagerFactory.getMetamodel();
<enter/>
Set<ManagedType<?>> managedTypes = mm.getManagedTypes();
assertEquals(managedTypes.size(), 1);
<enter/>
ManagedType itemType = managedTypes.iterator().next();
assertEquals(
    itemType.getPersistenceType(),
    Type.PersistenceType.ENTITY
);

You can get the Metamodel from either the EntityManagerFactory, of which you typically have only one instance in an application per data source, or, if it’s more convenient, from calling EntityManager#getMetamodel(). The set of managed types contains information about all persistent entities and embedded classes (which we’ll discuss in the next chapter). In this example, there’s only one: the Item entity. This is how you can dig deeper and find out more about each attribute.

Listing 3.11. Obtaining entity attribute information with the Metamodel API

Path: /examples/src/test/java/org/jpwh/test/simple/AccessJPAMetamodel.java

The attributes of the entity are accessed with a string: name and auctionEnd . This obviously isn’t type-safe, and if you change the names of the attributes, this code becomes broken and obsolete. The strings aren’t automatically included in the refactoring operations of your IDE.

JPA also offers a static type-safe metamodel.

Using a static metamodel

Java (at least up to version 8) has no first-class support for properties. You can’t access the fields or accessor methods of a bean in a type-safe fashion—only by their names, using strings. This is particularly inconvenient with JPA criteria querying, a type-safe alternative to string-based query languages. Here’s an example:

This query returns all items in the database; here there are two. If you now want to restrict this result and only return items with a particular name, you have to use a like expression, comparing the name attribute of each item with the pattern set in a parameter:

Notice how the namePath lookup requires the name string. This is where the type-safety of the criteria query breaks down. You can rename the Item entity class with your IDE’s refactoring tools, and the query will still work. But as soon as you touch the Item#name property, manual adjustments are necessary. Luckily, you’ll catch this when the test fails.

A much better approach, safe for refactoring and detecting mismatches at compile-time and not runtime, is the type-safe static metamodel:

The special class here is Item_; note the underscore. This class is a metadata class and lists all the attributes of the Item entity class:

@javax.persistence.metamodel.StaticMetamodel(Item.class)
public abstract class Item_ {
<enter/>
    public static volatile SingularAttribute<Item, Long> id;
    public static volatile SingularAttribute<Item, String> name;
    public static volatile SingularAttribute<Item, Date> auctionEnd;
<enter/>
}

You can write this class by hand or, as intended by the designers of this API, have it automatically generated by the annotation processing tool (apt) of the Java compiler. The Hibernate JPA2 Metamodel Generator (a distinct subproject of the Hibernate suite) uses this extension point. Its only purpose is to generate static metamodel classes from your managed persistent classes. You can download its JAR file and integrate it with your IDE (or your Maven build, as in the example code for this book). It will run automatically whenever you compile (or modify, depending on the IDE) the Item entity class and generate the appropriate Item_ metadata class.

What is the annotation processing tool (apt)?

Java includes the command-line utility apt, or annotation processing tool, which finds and executes annotation processors based on annotations in source code. An annotation processor uses reflection APIs to process program annotations (JSR 175). The apt APIs provide a build-time, source file, and read-only view of programs to model the Java type system. Annotation processors may first produce new source code and files, which apt can then compile along with the original source.

Although you’ve seen some mapping constructs in the previous sections, we haven’t introduced more sophisticated class and property mappings so far. You should now decide which mapping metadata strategy you’d like to use in your project—we recommend annotations, and XML only when necessary—and then read more about class and property mappings in the next chapter.

3.4. Summary

  • You’ve implemented persistent classes free of any crosscutting concerns like logging, authorization, and transaction demarcation; your persistent classes only depend on JPA at compile time. Even persistence-related concerns should not leak into the domain model implementation.
  • Transparent persistence is important if you want to execute and test your business objects independently and easily.
  • You’ve learned the best practices and requirements for the POJO and JPA entity programming model, and what concepts they have in common with the old JavaBean specification.
  • You’re ready to write more complex mappings, possibly with a combination of JDK annotations or JPA/Hibernate XML mapping files.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.31.163