4.4. Fine-grained models and mappings

After spending the first half of this chapter almost exclusively on entities and the respective basic persistent class-mapping options, we'll now focus on value types in their various forms. Two different kinds come to mind immediately: value-typed classes that came with the JDK, such as String or primitives, and value-typed classes defined by the application developer, such as Address and MonetaryAmount.

First, you map persistent class properties that use JDK types and learn the basic mapping elements and attributes. Then you attack custom value-typed classes and map them as embeddable components.

4.4.1. Mapping basic properties

If you map a persistent class, no matter whether it's an entity or a value type, all persistent properties have to be mapped explicitly in the XML mapping file. On the other hand, if a class is mapped with annotations, all of its properties are considered persistent by default. You can mark properties with the @javax.persistence.Transient annotation to exclude them, or use the transient Java keyword (which usually only excludes fields for Java serialization).

In a JPA XML descriptor, you can exclude a particular field or property:

<entity class="auction.model.User" access="FIELD">
    <attributes>
        ...
        <transient name="age"/>
    </attributes>
</entity>

A typical Hibernate property mapping defines a POJO's property name, a database column name, and the name of a Hibernate type, and it's often possible to omit the type. So, if description is a property of (Java) type java.lang.String, Hibernate uses the Hibernate type string by default (we come back to the Hibernate type system in the next chapter).

Hibernate uses reflection to determine the Java type of the property. Thus, the following mappings are equivalent:

<property name="description" column="DESCRIPTION" type="string"/>
<property name="description" column="DESCRIPTION"/>

It's even possible to omit the column name if it's the same as the property name, ignoring case. (This is one of the sensible defaults we mentioned earlier.)

For some more unusual cases, which you'll see more about later, you may need to use a <column> element instead of the column attribute in your XML mapping. The <column> element provides more flexibility: It has more optional attributes and may appear more than once. (A single property can map to more than one column, a technique we discuss in the next chapter.) The following two property mappings are equivalent:

<property name="description" column="DESCRIPTION" type="string"/>
<property name="description" type="string">
    <column name="DESCRIPTION"/>
</property>

The <property> element (and especially the <column> element) also defines certain attributes that apply mainly to automatic database schema generation. If you aren't using the hbm2ddl tool (see chapter 2, section 2.1.4, "Running and testing the application") to generate the database schema, you may safely omit these. However, it's preferable to include at least the not-null attribute, because Hibernate can then report illegal null property values without going to the database:

<property name="initialPrice" column="INITIAL_PRICE" not-null="true"/>

JPA is based on a configuration by exception model, so you could rely on defaults. If a property of a persistent class isn't annotated, the following rules apply:

  • If the property is of a JDK type, it's automatically persistent. In other words, it's handled like <property name="propertyName"/> in a Hibernate XML mapping file.

  • Otherwise, if the class of the property is annotated as @Embeddable, it's mapped as a component of the owning class. We'll discuss embedding of components later in this chapter.

  • Otherwise, if the type of the property is Serializable, its value is stored in its serialized form. This usually isn't what you want, and you should always map Java classes instead of storing a heap of bytes in the database. Imagine maintaining a database with this binary information when the application is gone in a few years.

If you don't want to rely on these defaults, apply the @Basic annotation on a particular property. The @Column annotation is the equivalent of the XML <column> element. Here is an example of how you declare a property's value as required:

@Basic(optional = false)
@Column(nullable = false)
public BigDecimal getInitialPrice { return initialPrice; }

The @Basic annotation marks the property as not optional on the Java object level. The second setting, nullable = false on the column mapping, is only responsible for the generation of a NOT NULL database constraint. The Hibernate JPA implementation treats both options the same way in any case, so you may as well use only one of the annotations for this purpose.

In a JPA XML descriptor, this mapping looks the same:

<entity class="auction.model.Item" access="PROPERTY">
    <attributes>
        ...
        <basic name="initialPrice" optional="false">
            <column nullable="false"/>
        </basic>
    </attributes>
</entity>

Quite a few options in Hibernate metadata are available to declare schema constraints, such as NOT NULL on a column. Except for simple nullability, however, they're only used to produce DDL when Hibernate exports a database schema from mapping metadata. We'll discuss customization of SQL, including DDL, in chapter 8, section 8.3, "Improving schema DDL." On the other hand, the Hibernate Annotations package includes a more advanced and sophisticated data validation framework, which you can use not only to define database schema constraints in DDL, but also for data validation at runtime. We'll discuss it in chapter 17.

Are annotations for properties always on the accessor methods?

Customizing property access

Properties of a class are accessed by the persistence engine either directly (through fields) or indirectly (through getter and setter property accessor methods). In XML mapping files, you control the default access strategy for a class with the default-access="field|property|noop|custom.Class" attribute of the hibernate-mapping root element. An annotated entity inherits the default from the position of the mandatory @Id annotation. For example, if @Id has been declared on a field, not a getter method, all other property mapping annotations, like the name of the column for the item's description property, are also declared on fields:

@Column(name = "ITEM_DESCR")
private String description;

public String getDescription() { return description; }

This is the default behavior as defined by the JPA specification. However, Hibernate allows flexible customization of the access strategy with the @org.hibernate.annotations.AccessType(<strategy>) annotation:

  • If AccessType is set on the class/entity level, all attributes of the class are accessed according to the selected strategy. Attribute-level annotations are expected on either fields or getter methods, depending on the strategy. This setting overrides any defaults from the position of the standard @Id annotations.

  • If an entity defaults or is explicitly set for field access, the AccessType("property") annotation on a field switches this particular attribute to runtime access through property getter/setter methods. The position of the AccessType annotation is still the field.

  • If an entity defaults or is explicitly set for property access, the AccessType("field") annotation on a getter method switches this particular attribute to runtime access through a field of the same name. The position of the AccessType annotation is still the getter method.

  • Any @Embedded class inherits the default or explicitly declared access strategy of the owning root entity class.

  • Any @MappedSuperclass properties are accessed with the default or explicitly declared access strategy of the mapped entity class.

You can also control access strategies on the property level in Hibernate XML mappings with the access attribute:

<property name="description"
          column="DESCR"
          access="field"/>

Or, you can set the access strategy for all class mappings inside a root <hibernate-mapping> element with the default-access attribute.

Another strategy besides field and property access that can be useful is noop. It maps a property that doesn't exist in the Java persistent class. This sounds strange, but it lets you refer to this "virtual" property in HQL queries (in other words, to use the database column in HQL queries only).

If none of the built-in access strategies are appropriate, you can define your own customized property-access strategy by implementing the interface org.hibernate.property.PropertyAccessor. Set the (fully qualified) class name on the access mapping attribute or @AccessType annotation. Have a look at the Hibernate source code for inspiration; it's a straightforward exercise.

Some properties don't map to a column at all. In particular, a derived property takes its value from an SQL expression.

Using derived properties

The value of a derived property is calculated at runtime by evaluating an expression that you define using the formula attribute. For example, you may map a totalIncludingTax property to an SQL expression:

<property name="totalIncludingTax"
          formula="TOTAL + TAX_RATE * TOTAL"
          type="big_decimal"/>

The given SQL formula is evaluated every time the entity is retrieved from the database (and not at any other time, so the result may be outdated if other properties are modified). The property doesn't have a column attribute (or subelement) and never appears in an SQL INSERT or UPDATE, only in SELECTs. Formulas may refer to columns of the database table, they can call SQL functions, and they may even include SQL subselects. The SQL expression is passed to the underlying database as is; this is a good chance to bind your mapping file to a particular database product, if you aren't careful and rely on vendor-specific operators or keywords.

Formulas are also available with a Hibernate annotation:

@org.hibernate.annotations.Formula("TOTAL + TAX_RATE * TOTAL")
public BigDecimal getTotalIncludingTax() {

    return totalIncludingTax;
}

The following example uses a correlated subselect to calculate the average amount of all bids for an item:

<property
   name="averageBidAmount"
   type="big_decimal"
   formula=
    "( select AVG(b.AMOUNT) from
       BID b where b.ITEM_ID = ITEM_ID )"/>

Notice that unqualified column names refer to columns of the table of the class to which the derived property belongs.

Another special kind of property relies on database-generated values.

Generated and default property values

Imagine a particular property of a class has its value generated by the database, usually when the entity row is inserted for the first time. Typical database-generated values are timestamp of creation, a default price for an item, and a trigger that runs for every modification.

Typically, Hibernate applications need to refresh objects that contain any properties for which the database generates values. Marking properties as generated, however, lets the application delegate this responsibility to Hibernate. Essentially, whenever Hibernate issues an SQL INSERT or UPDATE for an entity that has defined generated properties, it immediately does a SELECT afterwards to retrieve the generated values. Use the generated switch on a property mapping to enable this automatic refresh:

<property name="lastModified"
          column="LAST_MODIFIED"
          update="false"
          insert="false"
          generated="always"/>

Properties marked as database-generated must additionally be noninsertable and nonupdateable, which you control with the insert and update attributes. If both are set to false, the property's columns never appear in the INSERT or UPDATE statements—the property value is read-only. Also, you usually don't add a public setter method in your class for an immutable property (and switch to field access).

With annotations, declare immutability (and automatic refresh) with the @Generated Hibernate annotation:

@Column(updatable = false, insertable = false)
@org.hibernate.annotations.Generated(
    org.hibernate.annotations.GenerationTime.ALWAYS
)
private Date lastModified;

The settings available are GenerationTime.ALWAYS and GenerationTime.INSERT, and the equivalent options in XML mappings are generated="always" and generated="insert".

A special case of database-generated property values are default values. For example, you may want to implement a rule that every auction item costs at least $1. First, you'd add this to your database catalog as the default value for the INITIAL_PRICE column:

create table ITEM (
    ...
    INITIAL_PRICE number(10,2) default '1',
    ...
);

If you use Hibernate's schema export tool, hbm2ddl, you can enable this output by adding a default attribute to the property mapping:

<class name="Item" table="ITEM"
       dynamic-insert="true" dynamic-update="true">
    ...
    <property name="initialPrice" type="big_decimal">
        <column name="INITIAL_PRICE"
                default="'1'"
                generated="insert"/>
    </property>
    ...
 </class>

Note that you also have to enable dynamic insertion and update statement generation, so that the column with the default value isn't included in every statement if its value is null (otherwise a NULL would be inserted instead of the default value). Furthermore, an instance of Item that has been made persistent but not yet flushed to the database and not refreshed again won't have the default value set on the object property. In other words, you need to execute an explicit flush:

Item newItem = new Item(...);
session.save(newItem);

newItem.getInitialPrice();   // is null
session.flush();             // Trigger an INSERT
// Hibernate does a SELECT automatically

newItem.getInitialPrice();   // is $1

Because you set generated="insert", Hibernate knows that an immediate additional SELECT is required to read the database-generated property value.

You can map default column values with annotations as part of the DDL definition for a column:

@Column(name = "INITIAL_PRICE",
        columnDefinition = "number(10,2) default '1'")
@org.hibernate.annotations.Generated(
    org.hibernate.annotations.GenerationTime.INSERT
)
private BigDecimal initalPrice;

The columnDefinition attribute includes the complete properties for the column DDL, with datatype and all constraints. Keep in mind that an actual nonportable SQL datatype may bind your annotation mapping to a particular database management system.

We'll come back to the topic of constraints and DDL customization in chapter 8, section 8.3, "Improving schema DDL."

Next, you'll map user-defined value-typed classes. You can easily spot them in your UML class diagrams if you search for a composition relationship between two classes. One of them is a dependent class, a component.

4.4.2. Mapping components

So far, the classes of the object model have all been entity classes, each with its own lifecycle and identity. The User class, however, has a special kind of association with the Address class, as shown in figure 4.2.

In object-modeling terms, this association is a kind of aggregation—a part-of relationship. Aggregation is a strong form of association; it has some additional semantics with regard to the lifecycle of objects. In this case, you have an even stronger form, composition, where the lifecycle of the part is fully dependent upon the lifecycle of the whole.

Object modeling experts and UML designers claim that there is no difference between this composition and other weaker styles of association when it comes to the actual Java implementation. But in the context of ORM, there is a big difference: A composed class is often a candidate value type.

Figure 4-2. Relationships between User and Address using composition

You map Address as a value type and User as an entity. Does this affect the implementation of the POJO classes?

Java has no concept of composition—a class or attribute can't be marked as a component or composition. The only difference is the object identifier: A component has no individual identity, hence the persistent component class requires no identifier property or identifier mapping. It's a simple POJO:

public class Address {

    private String street;
    private String zipcode;
    private String city;

    public Address() {}

    public String getStreet() { return street; }
    public void setStreet(String street) { this.street = street; }

    public String getZipcode() { return zipcode; }
    public void setZipcode(String zipcode) {
        this.zipcode = zipcode; }

    public String getCity() { return city; }
    public void setCity(String city) { this.city = city; }
}

The composition between User and Address is a metadata-level notion; you only have to tell Hibernate that the Address is a value type in the mapping document or with annotations.

Component mapping in XML

Hibernate uses the term component for a user-defined class that is persisted to the same table as the owning entity, an example of which is shown in listing 4.2. (The use of the word component here has nothing to do with the architecture-level concept, as in software component.)

Listing 4-2. Mapping of the User class with a component Address
<class name="User" table="USER">

    <id name="id" column="USER_ID" type="long">
        <generator class="native"/>
    </id>

    <property name="loginName" column="LOGIN" type="string"/>

    <component name="homeAddress" class="Address"> 
<property name="street" type="string" column="HOME_STREET" not-null="true"/> <property name="city" type="string" column="HOME_CITY" not-null="true"/> <property name="zipcode" type="string" column="HOME_ZIPCODE" not-null="true"/> </component> <component name="billingAddress" class="Address">
<property name="street" type="string" column="BILLING_STREET" not-null="true"/> <property name="city" type="string" column="BILLING_CITY" not-null="true"/> <property name="zipcode" type="string" column="BILLING_ZIPCODE" not-null="true"/> </component> ... </class>

❶ You declare the persistent attributes of Address inside the <component> element. The property of the User class is named homeAddress.

❷ You reuse the same component class to map another property of this type to the same table.

Figure 4.3 shows how the attributes of the Address class are persisted to the same table as the User entity.

Figure 4-3. Table attributes of User with Address component

Notice that, in this example, you model the composition association as unidirectional. You can't navigate from Address to User. Hibernate supports both unidirectional and bidirectional compositions, but unidirectional composition is far more common. An example of a bidirectional mapping is shown in listing 4.3.

Listing 4-3. Adding a back-pointer to a composition
<component name="homeAddress" class="Address">
    <parent name="user"/>
    <property name="street" type="string"
              column="HOME_STREET" not-null="true"/>
    <property name="city" type="string"
              column="HOME_CITY" not-null="true"/>
    <property name="zipcode" type="stringshort"
              column="HOME_ZIPCODE" not-null="true"/>
</component>

In listing 4.3, the <parent> element maps a property of type User to the owning entity, which in this example is the property named user. You can then call Address.getUser() to navigate in the other direction. This is really a simple back-pointer.

A Hibernate component can own other components and even associations to other entities. This flexibility is the foundation of Hibernate's support for fine-grained object models. For example, you can create a Location class with detailed information about the home address of an Address owner:

<component name="homeAddress" class="Address">
    <parent name="user"/>

    <component name="location" class="Location">
        <property name="streetname" column="HOME_STREETNAME"/>
        <property name="streetside" column="HOME_STREETSIDE"/>
        <property name="housenumber" column="HOME_HOUSENR"/>
        <property name="floor" column="HOME_FLOOR"/>
    </component>

    <property name="city" type="string" column="HOME_CITY"/>
    <property name="zipcode" type="string" column="HOME_ZIPCODE"/>

</component>

The design of the Location class is equivalent to the Address class. You now have three classes, one entity, and two value types, all mapped to the same table.

Now let's map components with JPA annotations.

Annotating embedded classes

The Java Persistence specification calls components embedded classes. To map an embedded class with annotations, you can declare a particular property in the owning entity class as @Embedded, in this case the homeAddress of User:

@Entity
@Table(name = "USERS")
public class User {
    ...

    @Embedded
    private Address homeAddress;
    ...
}

If you don't declare a property as @Embedded, and it isn't of a JDK type, Hibernate looks into the associated class for the @Embeddable annotation. If it's present, the property is automatically mapped as a dependent component.

This is what the embeddable class looks like:

@Embeddable
public class Address {

    @Column(name = "ADDRESS_STREET", nullable = false)
    private String street;

    @Column(name = "ADDRESS_ZIPCODE", nullable = false)
    private String zipcode;

    @Column(name = "ADDRESS_CITY", nullable = false)
    private String city;

    ...
}

You can further customize the individual property mappings in the embeddable class, such as with the @Column annotation. The USERS table now contains, among others, the columns ADDRESS_STREET, ADDRESS_ZIPCODE, and ADDRESS_CITY. Any other entity table that contains component fields (say, an Order class that also has an Address) uses the same column options. You can also add a back-pointer property to the Address embeddable class and map it with @org.hibernate.annotations.Parent.

Sometimes you'll want to override the settings you made inside the embeddable class from outside for a particular entity. For example, here is how you can rename the columns:

@Entity
@Table(name = "USERS")
public class User {

    ...

    @Embedded
    @AttributeOverrides( {
        @AttributeOverride(name   = "street",
                           column = @Column(name="HOME_STREET") ),
        @AttributeOverride(name   = "zipcode",
                           column = @Column(name="HOME_ZIPCODE") ),
        @AttributeOverride(name   = "city",
                           column = @Column(name="HOME_CITY") )
    })
    private Address homeAddress;

    ...
}

The new @Column declarations in the User class override the settings of the embeddable class. Note that all attributes on the embedded @Column annotation are replaced, so they're no longer nullable = false.

In a JPA XML descriptor, a mapping of an embeddable class and a composition looks like the following:

<embeddable class="auction.model.Address access-type="FIELD"/>

<entity class="auction.model.User" access="FIELD">
    <attributes>
        ...
        <embedded name="homeAddress">
            <attribute-override name="street">
                <column name="HOME_STREET"/>
            </attribute-override>
            <attribute-override name="zipcode">
                <column name="HOME_ZIPCODE"/>
            </attribute-override>
            <attribute-override name="city">
                <column name="HOME_CITY"/>
            </attribute-override>
        </embedded>
    </attributes>
</entity>

There are two important limitations to classes mapped as components. First, shared references, as for all value types, aren't possible. The component homeAddress doesn't have its own database identity (primary key) and so can't be referred to by any object other than the containing instance of User.

Second, there is no elegant way to represent a null reference to an Address. In lieu of any elegant approach, Hibernate represents a null component as null values in all mapped columns of the component. This means that if you store a component object with all null property values, Hibernate returns a null component when the owning entity object is retrieved from the database.

You'll find many more component mappings (even collections of them) throughout the book.

..................Content has been hidden....................

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