Skip to main content

Domain Model Metadata

Posted by manning_pubs on April 9, 2013 at 1:18 PM PDT



Domain Model Metadata

by Christian Bauer, Gavin King, and Gary Gregory, authors of Java Persistence with Hibernate, Second Edition

The Java Platform, Enterprise Edition (Java EE) standard solves the problem of leaky concerns with metadata as annotations in your code or as external XML descriptors. This approach allows the runtime container to implement predefined cross-cutting concerns—security, concurrency, persistence, transactions, and remoteness—in a generic way by intercepting calls to your application components. In this article, based on chapter 3 of Java Persistence with Hibernate, Second Edition, the authors discuss 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 access domain model metadata.

Object-relational mapping (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 ORM metadata governs the transformation between the different type systems and relationship representations in object-oriented and SQL systems. The Java Persistence API (JPA) includes 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. Hibernate also has its own native metadata API that allows modification of the mapped SQL database table and column names dynamically. As an engineer, it’s your job to create and maintain this information.

JPA standardizes two metadata options: annotations in Java code and externalized XML descriptor files. Hibernate provides extensions for native functionality, also available as annotations and/or XML descriptors. Usually you choose either annotations or XML files as the primary source of mapping metadata. After reading this article, you’ll have the background information to make an educated decision about your own project. Most engineers today prefer Java annotations as the primary mechanism for declaring metadata. 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.

Creating annotation-based metadata

The big advantage of using annotations is that they allow you to put metadata next to the information it describes instead of separating it physically in a different file. Let’s look at an example.

\model\src\main\java\org\jpwh\model\simple\Item.java
import javax.persistence.Entity;


@Entity

public class Item {


}

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.

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 your application starts. Your 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 JPA-dependent?

Yes, your class is JPA-dependent, but it’s a compile-time-only dependency. You need JPA libraries on your classpath when compiling the source of your domain model class. The JPA 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) do 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

You could map most of your application’s model with JPA-compatible annotations from the javax.persistence package, but you’d still 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 chose Hibernate.

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

import javax.persistence.Entity;

@Entity
@org.hibernate.annotations.Cache(   
    usage = org.hibernate.annotations.CacheConcurrencyStrategy.READ_WRITE
)
public class Item {

}

We prefer to prefix Hibernate annotations with the full org.hibernate.annotations package name. Consider this good practice because you can now easily distinguish between JPA-specification and vendor-specific metadata for this class. 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 to a different Java Persistence provider, you only have to replace the vendor-specific extensions for which you can expect a similar feature set to be available with most mature JPA implementations. We hope you’ll never have to do this, and it doesn’t often happen in practice—but be prepared.

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

Creating global annotation metadata

The @Entity annotation maps a particular class. JPA and Hibernate also have annotations for global metadata. For example, a named query 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 prefer to keep global metadata in a separate file. A good choice is package-level annotations; they’re in a file called package-info.java in a particular package directory. An example of global named query declarations is shown here.

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

file\model\src\main\java\org\jpwh\model\querying\package-info.java
@NamedQueries(
    {
   
        @NamedQuery(
       
           name = “findItemsOrderByName”,
       
           query = “select i from Item i order by i.name asc”
   
        )
   
        ,
   
        NamedQuery(
       
           name = “findItemBuyNowPriceGreaterThan”,
       
           query = “select i from Item i where i.buyNowPrice > :price”,
       
           timeout = 60, // Seconds!
       
           comment = “Custom SQL comment”
   
        )

    }
)


package org.jpwh.model.querying;

import org.hibernate.annotations.NamedQueries;
import org.hibernate.annotations.NamedQuery;

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.

This code example includes only annotations from the Hibernate package and no Java Persistence annotations. We ignored the standardized @org.javax.persistence.NamedQuery annotation of JPA and used the Hibernate alternative for a reason. 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. Because the native Hibernate annotations offer the same, and sometimes more, functionality, this JPA restriction shouldn’t be too much of a problem.

If you don’t want to use the Hibernate annotations, you’ll either have to 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.
Another use of annotations is to improve the domain model classes with validation rules.

Applying Bean Validation rules

Most applications contain a multitude of data integrity checks. You’ve seen what happens when you violate one of the most simple data integrity constraints: you get a NullPointerException when you expect a value to be available. Other integrity check examples are a string-valued property that can’t be empty (remember, an empty string isn’t null), a string that must match a particular regular expression pattern, or 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 layer has to 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 at 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 really a collection of integrity rules—you might think of Bean Validation constraints as additional ORM metadata.

Have a look at the extended Item domain model class, shown here.

Listing 2 Applying validation constraints on Item entity fields

\model\src\main\java\org\jpwh\model\simple\Item.javaimport 
import javax.validation.constraints.Future;
import javax.validation.constraints.NotNull;

import javax.validation.constraints.Size;


@Entity

public class Item {
 

   @NotNull
  
   @Size(
      
       min = 2,
      
       max = 255,
      
       message = “Name is required, maximum 255 characters.”
  
   )
  
   protected String name;
  

   @Future
  
   protected Date auctionEnd;

}

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

The validation engine accesses 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. Constraints are then part of the classes’ API and are included in its Javadoc, making your domain model implementation easier to understand. Note that this is independent of access by the JPA provider; 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 use class-level annotations and validate several attribute values at the same time on an instance of the class.

The test code shown in the following listing shows how you can manually check the integrity of an Item instance.

Listing 3 Testing an Item instance for constraint violations

\examples\src\test\java\org\jpwh\test\simple\ModelOperations.java
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

Validator validator = factory.getValidator();


Item item = new Item();

item.setName(“Some Item”);

item.setAuctionEnd(new Date());


Set> violations = validator.validate(item);


// We have one validation error: auction end date wasn't in the future!

assertEquals(1, violations.size());


ConstraintViolation violation = violations.iterator().next();

String failedPropertyName =
        violation.getPropertyPath().iterator().next().getName();


assertEquals(failedPropertyName, “auctionEnd”);

assertEquals(violation.getMessage(), “must be in the future”);

We won’t explain this code in detail, but we offer it for you to explore. You’ll rarely write this kind of validation code; most of the time your user interface and persistence framework handle this aspect automatically. It’s therefore important to look for Bean Validation integration when selecting a UI framework. JavaServer Faces (JSF) version 2 and newer automatically integrates with Bean Validation, for example.

As required for all JPA providers, Hibernate automatically integrates with Hibernate Validator if the libraries are available on the classpath. Hibernate offers the following validation 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 element in your persistence.xml configuration file:

  • AUTO mode (default)—Hibernate validates only if it finds a Bean Validation provider (such as Hibernate Validator) on the classpath of the running application.
  • CALLBACK mode—Validation always occurs, and you’ll get a deployment error if you forget to bundle a Bean Validation provider.
  • NONE mode—Disables automatic validation by the JPA provider.

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 or the metadata API for discovery of constraints. The Java Persistence and Bean Validation standards embrace annotations aggressively. Still, the expert groups are aware of the advantages of XML deployment descriptors in certain situations; for example, configuration metadata that changes with each deployment.

Summary

We talked about mapping metadata options—the ways you can inform 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 are eventually deployed with the compiled Java classes and are accessed by Hibernate at runtime.


Here are some other Manning titles you might be interested in:

Making Java Groovy

Making Java Groovy
Kenneth A. Kousen

The Well-Grounded Java Developer

The Well-Grounded Java Developer
Benjamin J. Evans and Martijn Verburg

Groovy in Action, Second Edition

Groovy in Action, Second Edition
Dierk König, Guillaume Laforge, Paul King, Jon Skeet, and Hamlet D’Arcy


AttachmentSize
jpwh2001.png4.34 KB
jpwh2002.png4.19 KB
jpwh2003.png4.21 KB
Related Topics >>