Property Mappings
So far, we have only shown how to specify column mappings for simple primitive types. There are still a few bits of metadata that you can use to fine-tune your mappings. In this section, you'll learn more annotations for more complex property mappings.
Java Persistence has mappings for JDBC Blobs and Clob
s, serializable objects, and embeddable objects, as well as optimistic concurrency with version properties. We will discuss all of these.
@Transient
In our first example of our Customer bean class, we showed that the persistence manager would assume that every nontransient property (getter/setter or field, depending on your access type) in your bean class is persistent, even if the property does not have any mapping metadata associated with it. This is great for fast prototyping of your persistent objects, especially when your persistence vendor supports autotable generation. However, you may have properties that you don't want to be persistent, and, therefore, this default behavior is inappropriate. For instance, let's take our @EmbeddedId example. The interface for that class has you obtaining a CustomerPK
class instance if you want to view the last name or Social Security number of your customer. It may be nicer to have getter methods on the Customer bean class that directly provide that level of information. This is where the @javax.persistence.Transient
annotation comes in:
@Entity
public class Customer implements java.io.Serializable {
private String firstName;
private CustomerPK pk;
public String getFirstName( ) { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
@Transient
public String getLastName( ) { return pk.getLastName( ); }
@Transient
public long getSsn( ) { return pk.getSsn( ); }
@EmbeddedId
public PK getPk( ) { return pk; }
public void setPk(CustomerPK pk) { this.pk = pk; }
}
When you annotate a property with @javax.persistence.Transient
, the persistence manager ignores it and doesn't treat it as persistent. Here is what the XML equivalent looks like:
<entity-mappings>
<embeddable class="com.titan.domain.CustomerPK" access-type="PROPERTY">
<embeddable-attribute name="lastName">
<column name="CUSTOMER_LAST_NAME"/>
</embeddable-attribute>
<embeddable-attribute name="ssn">
<column name="CUSTOMER_SSN"/>
</embeddable-attribute>
</embeddable>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<embedded-id name="pk"/>
<transient name="lastName"/>
<transient name="ssn"/>
</attributes>
</entity>
</entity-mappings>
The <transient> element is used within an <attribute> declaration to identify @transient
properties.
@Basic and FetchType
The @Basic annotation is the simplest form of mapping for a persistent property. This is the default mapping type for properties which are primitives, primitive wrapper types, java.lang.String,
byte[], Byte[], char[], Character[], java.math.BigInteger,
java.math.BigDecimal,
java.util.Date,
java.util.Calendar,
java.sql.Date,
java.sql.Time
, and java.sql.Timestamp
. You do not need to tell your persistence manager explicitly that you're mapping a basic property because it can usually figure out how to map it to JDBC using the property's type.
public @interface Basic
{
FetchType fetch( ) default EAGER;
boolean optional( ) default true;
}
public enum FetchType
{
LAZY, EAGER
}
Usually, you would never annotate your properties with this annotation. However, at times you may need to specify the fetch( ) attribute, which allows you to specify whether a particular property is loaded lazily or eagerly when the persistent object is first fetched from the database. This attribute allows your persistence provider to optimize your access to the database by minimizing the amount of data you load with a query. So, if the fetch( ) attribute is LAZY
, that particular property will not be initialized until you actually access this field. All other mapping annotations have this same attribute. The weird thing about the specification, though, is that the fetch( ) attribute is just a hint. Even if you mark the property as LAZY for a @Basic type, the persistence provider is still allowed to load the property eagerly. This is due to the fact that this feature requires class-level instrumentation. It should also be noted that lazy loading is neither really useful nor a significant performance optimization. It is best practice to eagerly load basic properties.
The optional( ) attribute is useful for when the persistence provider is generating the database schema for you. When this attribute is set to true, the property is treated as nullable. Let's take our Customer entity and show how to use the @Basic annotation:
package com.titan.domain
import javax.persistence.*;
@Entity
public class Customer implements java.io.Serializable {
private long id;
private String firstName;
private String lastName;
@Id
@GeneratedValue
public long getId( ) { return id; }
public void setId(long id) { this.id = id; }
@Basic(fetch=FetchType.LAZY, optional=false)
public String getFirstName( ) { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
@Basic(fetch=FetchType.LAZY, optional=false)
public String getLastName( ) { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
}
In this code, we hint that both the firstName and lastName properties are lazily loadable. Also, these properties are not nullable in the database schema.
The @Basic annotation also has an XML equivalent:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<basic name="firstName" fetch="LAZY" optional="false"/>
<basic name="lastName" fetch="LAZY" optional="false"/>
</attributes>
</entity>
</entity-mappings>
@Temporal
The @Temporal annotation provides additional information to the persistence provider about the mapping of a java.util.Date
or java.util.Calendar
property. This annotation allows you to map these object types to a date, a time, or a timestamp field in the database. By default, the persistence provider assumes that the temporal type is a timestamp:
package javax.persistence;
public enum TemporalType
{
DATE,
TIME,
TIMESTAMP
}
public @interface Temporal
{
TemporalType value( ) default TIMESTAMP;
}
This annotation can be used in conjunction with the @Basic annotation. For example, say we want to add a time-created property to the Customer entity. We would do it as follows:
package com.titan.domain;
import javax.persistence.*;
@Entity
public class Customer implements java.io.Serializable {
private long id;
private String firstName;
private String lastName;
private java.util.Date timeCreated;
@Id
@GeneratedValue
public long getId( ) { return id; }
public void setId(long id) { this.id = id; }
public String getFirstName( ) { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName( ) { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Temporal(TemporalType.TIME)
public java.util.Date getTimeCreated( ) { return timeCreated; }
public void setTimeCreated(java.util.Date time) { timeCreated = time; }
}
The timeCreated
property is stored in the database as a TIME
SQL type. Let's look at the XML equivalent:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<basic name="timeCreated"/>
<temporal>TIME</temporal>
</basic>
</attributes>
</entity>
</entity-mappings>
The <temporal> element is used within a <basic> element definition and can have the same values as the enumeration type discussed earlier.
@Lob
Sometimes your persistent properties require a lot of memory. One of your fields may represent an image or the text of a very large document. JDBC has special types for these very large objects. The java.sql.Blob
type represents binary data, and java.sql.Clob
represents character data. The @javax.persistence.Lob
annotation is used to map these large object types. Java Persistence allows you to map some basic types to a @Lob and have the persistence manager handle them internally as either a Blob or a Clob, depending on the type of the property:
package javax.persistence;
public @interface Lob
{
}
Properties annotated with a @Lob are persisted in a:
Blob if the Java type is byte[], Byte[], or java.io.Serializable
Clob if the Java type is char[], Character[], or java.lang.String
The @Lob annotation is usually used in conjunction with the @Basic annotation to hint that the property should be lazily loaded. Let's modify our Customer bean to add a property that represents a JPEG image of that customer:
package com.titan.domain;
import javax.persistence.*;
import com.acme.imaging.JPEG;
@Entity
public class Customer implements java.io.Serializable {
private long id;
private String firstName;
private String lastName;
private JPEG picture;
@Id
@GeneratedValue
public long getId( ) { return id; }
public void setId(long id) { this.id = id; }
public String getFirstName( ) { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName( ) { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Lob
@Basic(fetch=FetchType.LAZY)
public JPEG getPicture( ) { return picture; }
public void setPicture(JPEG picture) { this.picture = picture; }
}
When using @Lob types, it is probably best to mark the fetch type as lazy, since you usually won't access these large objects when interacting with a Customer bean.
Let's look at the XML equivalent to @Lob:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<basic name="picture">
<lob/>
</basic>
</attribute>s
</entity>
</entity-mappings>
The <lob> element is used within a <basic> element and identifies the property as a @Lob type.
@Enumerated
The @Enumerated annotation maps Java enum types to the database. It is used in conjunction with the @Basic annotation and lets you specify additional fetch semantics:
package javax.persistence;
public enum EnumType
{
ORDINAL,
STRING
}
public @interface Enumerated
{
EnumType
value( ) default ORDINAL;
}
A Java enum
property can be mapped either to the string representation or to the numeric ordinal number of the enum value. For example, let's say we want a Customer entity property that designates the kind of customer that is purchasing a reservation. This could be represented in a Java enum called CustomerType with the enum values UNREGISTERED,
REGISTERED,
or BIG_SPENDAH. We would do it as follows:
package com.titan.domain;
import javax.persistence.*;
public enum CustomerType
{
UNREGISTERED,
REGISTERED,
BIG_SPENDAH
}
@Entity
public class Customer implements java.io.Serializable {
private long id;
private String firstName;
private String lastName;
private CustomerType customerType;
@Id
@GeneratedValue
public long getId( ) { return id; }
public void setId(long id) { this.id = id; }
public String getFirstName( ) { return firstName; }
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getLastName( ) { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
@Enumerated
(EnumType.STRING)
public CustomerType getCustomerType( ) { return customerType; }
public void setCustomerType(CustomerType type) { customerType = type; }
}
You are not required to use the @Enumerated annotation to map a property. If you omit this annotation, the ORDINAL EnumType value is assumed.
Here's the XML equivalent:
<entity-mappings>
<entity class="com.titan.domain.Customer" access="PROPERTY">
<attributes>
<id name="id">
<generated-value/>
</id>
<basic name="customerType">
<enumerated>STRING</enumerated>
</basic>
</attributes>
</entity>
</entity-mappings>
The <enumerated> element is used within an <attribute> element and can have the ORDINAL
or STRING value.
 |