Java Persistence/Basic Attributes

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Basics[edit | edit source]

A basic attribute is one where the attribute class is a simple type such as String, Number, Date or a primitive. A basic attribute's value can map directly to the column value in the database. The following table summarizes the basic types and the database types they map to.

Java typeDatabase type
String (char, char[]) VARCHAR (CHAR, VARCHAR2, CLOB, TEXT)
Number (BigDecimal, BigInteger, Integer, Double, Long, Float, Short, Byte) NUMERIC (NUMBER, INT, LONG, FLOAT, DOUBLE)
int, long, float, double, short, byte NUMERIC (NUMBER, INT, LONG, FLOAT, DOUBLE)
byte[] VARBINARY (BINARY, BLOB)
boolean (Boolean) BOOLEAN (BIT, SMALLINT, INT, NUMBER)
java.util.Date TIMESTAMP (DATE, DATETIME)
java.sql.Date DATE (TIMESTAMP, DATETIME)
java.sql.Time TIME (TIMESTAMP, DATETIME)
java.sql.Timestamp TIMESTAMP (DATETIME, DATE)
java.util.Calendar TIMESTAMP (DATETIME, DATE)
java.lang.Enum NUMERIC (VARCHAR, CHAR)
java.util.Serializable VARBINARY (BINARY, BLOB)


In JPA a basic attribute is mapped through the @Basic annotation or the <basic> element. The types and conversions supported depend on the JPA implementation and database platform. Some JPA implementations may support conversion between many different data-types or additional types, or have extended type conversion support, see the advanced section for more details. Any basic attribute using a type that does not map directly to a database type can be serialized to a binary database type.

The easiest way to map a basic attribute in JPA is to do nothing. Any attributes that have no other annotations and do not reference other entities will be automatically mapped as basic, and even serialized if not a basic type. The column name for the attribute will be defaulted, named the same as the attribute name, as uppercase. Sometimes auto-mapping can be unexpected if you have an attribute in your class that you did not intend to have persisted. You must mark any such non-persistent fields using the @Transient annotation or <transient> element.

Although auto-mapping makes rapid prototyping easy, you typically reach a point where you want control over your database schema. To specify the column name for a basic attribute the @Column annotation or <column> element is used. The column annotation also allows for other information to be specified such as the database type, size, and some constraints.

Example of basic mapping annotations[edit | edit source]

@Entity
public class Employee {
  // Id mappings are also basic mappings.
  @Id
  @Column(name="ID")
  private long id;       
  @Basic
  @Column(name="F_NAME")
  private String firstName;
  // The @Basic is not required in general because it is the default.
  @Column(name="L_NAME")
  private String lastName;
  // Any un-mapped field will be automatically mapped as basic and column name defaulted.
  private BigDecimal salary;       
  // Non-persistent fields must be marked as transient.
  @Transient
  private EmployeeService service;
  ...
}

Example of basic mapping XML[edit | edit source]

<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <attributes>
        <id name="id">
            <column name="ID"/>
        </id>
        <basic name="firstName">
            <column name="F_NAME"/>
        </basic>
        <basic name="lastName">
            <column name="L_NAME"/>
        </basic>
        <transient name="service"/>
    </attributes>
</entity>

Common Problems[edit | edit source]

Translating Values[edit | edit source]

See Conversion

Truncated Data[edit | edit source]

A common issue is that data, such as Strings, written from the object are truncated when read back from the database. This is normally caused by the column length not being large enough to handle the object's data. In Java there is no maximum size for a String, but in a database VARCHAR field, there is a maximum size. You must ensure that the length you set in your column when you create the table is large enough to handle any object value. For very large Strings CLOBs can be used, but in general CLOBs should not be over used, as they are less efficient than a VARCHAR.
If you use JPA to generate your database schema, you can set the column length through the Column annotation or element, see Column Definition and Schema Generation.

How to map timestamp with timezones?[edit | edit source]

See Timezones

How to map XML data-types?[edit | edit source]

See Custom Types

How to map Struct and Array types?[edit | edit source]

See Custom Types

How to map custom database types?[edit | edit source]

See Custom Types

How to exclude fields from INSERT or UPDATE statements, or default values in triggers?[edit | edit source]

See Insertable, Updatable

Advanced[edit | edit source]

Temporal, Dates, Times, Timestamps and Calendars[edit | edit source]

Dates, times, and timestamps are common types both in the database and in Java, so in theory mappings these types should be simple, right? Well sometimes this is the case and just a normal Basic mapping can be used, however sometimes it becomes more complex.

Some databases do not have DATE and TIME types, only TIMESTAMP fields, however some do have separate types, and some just have DATE and TIMESTAMP. Originally in Java 1.0, Java only had a java.util.Date type, which was both a date, time and milliseconds. In Java 1.1 this was expanded to support the common database types with java.sql.Date, java.sql.Time, and java.sql.Timestamp, then to support internationalization Java created the java.util.Calendar type and virtually deprecated (almost all of the methods) the old date types (which JDBC still uses).

If you map a Java java.sql.Date type to a database DATE, this is just a basic mapping and you should not have any issues (ignore Oracle's DATE type that is/was a timestamp for now). You can also map java.sql.Time to TIME, and java.sql.Timestamp to TIMESTAMP. However if you have a java.util.Date or java.util.Calendar in Java and wish to map it to a DATE or TIME, you may need to indicate that the JPA provider perform some sort of conversion for this. In JPA the @Temporal annotation or <temporal> element is used to map this. You can indicate that just the DATE or TIME portion of the date/time value be stored to the database. You could also use Temporal to map a java.sql.Date to a TIMESTAMP field, or any other such conversion.

Example of temporal annotation[edit | edit source]

@Entity
public class Employee {
    ...
    @Basic
    @Temporal(DATE)
    private Calendar startDate;
    ...
}

Example of temporal XML[edit | edit source]

<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <attributes>
        ...
        <basic name="startDate">
            <temporal>DATE</temporal>
        </basic>
    </attributes>
</entity>

Milliseconds[edit | edit source]

The precision of milliseconds is different for different temporal classes and database types, and on different databases. The java.util.Date and Calendar classes support milliseconds. The java.sql.Date and java.sql.Time classes do not support milliseconds. The java.sql.Timestamp class supports nanoseconds.

On many databases the TIMESTAMP type supports milliseconds. On Oracle prior to Oracle 9, there was only a DATE type, which was a date and a time, but had no milliseconds. Oracle 9 added a TIMESTAMP type that has milliseconds (and nanoseconds), and now treats the old DATE type as only a date, so be careful using it as a timestamp. MySQL has DATE, TIME and DATETIME types. DB2 has a DATE, TIME and TIMESTAMP types, the TIMESTAMP supports microseconds. Sybase and SQL Server just have a DATETIME type which has milliseconds, but at least on some versions has precision issues, it seems to store an estimate of the milliseconds, not the exact value.

If you use timestamp version locking you need to be very careful of your milliseconds precision. Ensure your database supports milliseconds precisely otherwise you may have issues, especially if the value is assigned in Java, then differs what gets stored on the database, which will cause the next update to fail for the same object.

In general I would not recommend using a timestamp and as primary key or for version locking. There are too many database compatibility issues, as well as the obvious issue of not supporting two operations in the same millisecond.

Timezones[edit | edit source]

Temporals become a lot more complex when you start to consider time zones, internationalization, eras, locals, day-light savings time, etc. In Java only Calendar supports time zones. Normally a Calendar is assumed to be in the local time zone, and is stored and retrieved from the database with that assumption. If you then read that same Calendar on another computer in another time zone, the question is if you will have the same Calendar or will you have the Calendar of what the original time would have been in the new time zone? It depends on if the Calendar is stored as the GMT time, or the local time, and if the time zone was stored in the database.

Some databases support time zones, but most database types do not store the time zone. Oracle has two special types for timestamps with time zones, TIMESTAMPTZ (time zone is stored) and TIMESTAMPLTZ (local time zone is used). Some JPA providers may have extended support for storing Calendar objects and time zones.

TopLink, EclipseLink : Support the Oracle TIMESTAMPTZ and TIMESTAMPLTZ types using the @TypeConverter annotation and XML.

Forum Posts

Joda-Time[edit | edit source]

Joda-Time is a commonly used framework for date/time usage in Java. It replaces Java Calendars which many people find difficult to use and have poor performance. There is no standard Joda-Time support in JPA, but a Converter can be used to convert from Joda-Time classes and database types.

TopLink, EclipseLink : The base product offers no specific Joda-Time support, but there is a custom converter provided by a third party library, joda-time-eclipselink-integration.

Enums[edit | edit source]

Java Enums are typically used as constants in an object model. For example an Employee may have a gender of enum type Gender (MALE, FEMALE).

By default in JPA an attribute of type Enum will be stored as a Basic to the database, using the integer Enum values as codes (i.e. 0, 1). JPA also defines an @Enumerated annotation and <enumerated> element (on a <basic>) to define an Enum attribute. This can be used to store the Enum as the STRING value of its name (i.e. "MALE", "FEMALE").

For translating Enum types to values other than the integer or String name, such as character constants, see Translating Values.

Example of enumerated annotation[edit | edit source]

public enum Gender {
    MALE,
    FEMALE
}

@Entity
public class Employee {
    ...
    @Basic
    @Enumerated(EnumType.STRING)
    private Gender gender;
    ...
}

Example of enumerated XML[edit | edit source]

<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <attributes>
        ...
        <basic name="gender">
            <enumerated>STRING</enumerated>
        </basic>
    </attributes>
</entity>

LOBs, BLOBs, CLOBs and Serialization[edit | edit source]

A LOB is a Large OBject, such as a BLOB (Binary LOB), or a CLOB (Character LOB). It is a database type that can store a large binary or string value, as the normal VARCHAR or VARBINARY types typically have size limitations. A LOB is often stored as a locator in the database table, with the actual data stored outside of the table. In Java a CLOB will normally map to a String, and a BLOB will normally map to a byte[], although a BLOB may also represent some serialized object.

By default in JPA any Serializable attribute that is not a relationship or a basic type (String, Number, temporal, primitive), will be serialized to a BLOB field.

JPA defines the @Lob annotation and <lob> element (on a <basic>) to define that an attribute maps to a LOB type in the database. The annotation is just a hint to the JPA implementation that this attribute will be stored in a LOB, as LOBs may need to be persisted specially. Sometimes just mapping the LOB as a normal Basic will work fine as well.

Various databases and JDBC drivers have various limits for LOB sizes. Some JDBC drivers have issues beyond 4k, 32k or 1meg. The Oracle thin JDBC drivers had a 4k limitation in some versions for binding LOB data. Oracle provided a workaround for this limitation, which some JPA providers support. For reading LOBs, some JDBC drivers prefer using streams, some JPA providers also support this option.

Typically the entire LOB will be read and written for the attribute. For very large LOBs reading the value always, or reading the entire value may not be desired. The fetch type of the Basic could be set to LAZY to avoid reading a LOB unless accessed. Support for LAZY fetching on Basic is optional in JPA, so some JPA providers may not support it. A workaround, which is often a good idea in general given the large performance cost of LOBs, is to store the LOB in a separate table and class and define a OneToOne to the LOB object instead of a Basic. If the entire LOB is never desired to be read, then it should not be mapped. It is best to use direct JDBC to access and stream the LOB in this case. It may be possible to map the LOB to a java.sql.Blob/java.sql.Clob in your object to avoid reading the entire LOB, but these require a live connection, so may have issues with detached objects.

Example of lob annotation[edit | edit source]

@Entity
public class Employee {
    ...
    @Basic(fetch=FetchType.LAZY)
    @Lob
    private byte[] picture;
    ...
}

Example of lob XML[edit | edit source]

<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <attributes>
        ...
        <basic name="picture" fetch="LAZY">
            <lob/>
        </basic>
    </attributes>
</entity>

Lazy Fetching[edit | edit source]

The fetch attribute can be set on a Basic mapping to use LAZY fetching. By default all Basic mappings are EAGER, which means the column is selected whenever the object is selected. By setting the fetch to LAZY, the column will not be selected with the object. If the attribute is accessed, then the attribute value will be selected in a separate database select. Support for LAZY is an optional feature of JPA, so some JPA providers may not support it. Typically support for lazy on basics will require some form of byte code weaving, or dynamic byte code generation, which may have issues in certain environments or JVMs, or may require preprocessing your application's persistence unit jar.

Only attributes that are rarely accessed should be marked lazy, as accessing the attribute causes a separate database select, which can hurt performance. This is especially true if a large number of objects is queried. The original query will require one database select, but if each object's lazy attribute is accessed, this will require n database selects, which can be a major performance issue.

Using lazy fetching on basics is similar to the concept of fetch groups. Lazy basics is basically support for a single default fetch group. Some JPA providers support fetch groups in general, which allow more sophisticated control over what attributes are fetched per query.

TopLink, EclipseLink : Support lazy basics and fetch groups. Fetch groups can be configured through the EclipseLink API using the FetchGroup class.

Optional[edit | edit source]

A Basic attribute can be optional if its value is allowed to be null. By default everything is assumed to be optional, except for an Id, which can not be optional. Optional is basically only a hint that applies to database schema generation, if the persistence provider is configured to generate the schema. It adds a NOT NULL constraint to the column if false. Some JPA providers also perform validation of the object for optional attributes, and will throw a validation error before writing to the database, but this is not required by the JPA specification. Optional is defined through the optional attribute of the Basic annotation or element.

Column Definition and Schema Generation[edit | edit source]

There are various attributes on the Column annotation and element for database schema generation. If you do not use JPA to generate your schema you can ignore these. Many JPA providers do provide the feature of auto generation of the database schema. By default the Java types of the object's attributes are mapped to their corresponding database type for the database platform you are using. You may require configuring your database platform with your provider (such as a persistence.xml property) to allow schema generation for your database, as many database use different type names.

The columnDefinition attribute of Column can be used to override the default database type used, or enhance the type definition with constraints or other such DDL. The length, scale and precision can also be set to override defaults. Since the defaults for the length are just defaults, it is normally a good idea to set these to be correct for your data model's expected data, to avoid data truncation. The unique attribute can be used to define a unique constraint on the column, most JPA providers will automatically define primary key and foreign key constraints based on the Id and relationship mappings.

JPA does not define any options to define an index. Some JPA providers may provide extensions for this. You can also create your own indexes through native queries

Example of column annotations[edit | edit source]

@Entity
public class Employee {
  @Id
  @Column(name="ID")
  private long id;
  @Column(name="SSN", unique=true, nullable=false, description="description")
  private long ssn;
  @Column(name="F_NAME", length=100)
  private String firstName;
  @Column(name="L_NAME", length=200)
  private String lastName;
  @Column(name="SALARY", scale=10, precision=2)
  private BigDecimal salary;       
  @Column(name="S_TIME", columnDefinition="TIMESTAMPTZ")
  private Calendar startTime;
  @Column(name="E_TIME", columnDefinition ="TIMESTAMPTZ")
  private Calendar endTime;
  ...
}

Example of column XML[edit | edit source]

<entity name="Employee" class="org.acme.Employee" access="FIELD">
    <attributes>
        <id name="id">
            <column name="ID"/>
        </id>
        <basic name="ssn">
            <column name="SSN" unique="true" optional="false"/>
        </basic>
        <basic name="firstName">
            <column name="F_NAME" length="100"/>
        </basic>
        <basic name="lastName">
            <column name="L_NAME" length="200"/>
        </basic>
        <basic name="startTime">
            <column name="S_TIME" columnDefinition="TIMESTAMPTZ"/>
        </basic>
        <basic name="endTime">
            <column name="E_TIME" columnDefinition="TIMESTAMPTZ"/>
        </basic>
    </attributes>
</entity>

If using BigDecimal with Postgresql, JPA maps salary to a table column of type NUMERIC(38,0). You can adjust scale and precision for BigDecimal within the @Column annotation.

   @Column(precision=8, scale=2)
   private BigDecimal salary;

Insertable, Updatable / Read Only Fields / Returning[edit | edit source]

The Column annotation and XML element defines insertable and updatable options. These allow for this column, or foreign key field to be omitted from the SQL INSERT or UPDATE statement. These can be used if constraints on the table prevent insert or update operations. They can also be used if multiple attributes map to the same database column, such as with a foreign key field through a ManyToOne and Id or Basic mapping. Setting both insertable and updatable to false, effectively mark the attribute as read-only.

insertable and updatable can also be used in the database table defaults, or auto assigns values to the column on insert or update. Be careful in doing this though, as this means that the object's values will be out of synch with the database, unless it is refreshed. For IDENTITY or auto assigned id columns a GeneratedValue should normally be used, instead of setting insertable to false. Some JPA providers also support returning auto assigned fields values from the database after insert or update operations. The cost of refreshing or returning fields back into the object can affect performance, so it is normally better to initialize field values in the object model, not in the database.

TopLink, EclipseLink : Support returning insert and update values back into the object using the ReturnInsert and ReturnUpdate annotations and XML elements.

Converters (JPA 2.1)[edit | edit source]

A common problem in storing values to the database is that the value desired in Java differs from the value used in the database. Common examples include using a boolean in Java and a 0, 1 or a 'T', 'F' in the database. Other examples are using a String in Java and a DATE in the database, or mapping custom Java types such as Joda-Time types, or a Money type.

JPA 2.1 defines the @Converter, @Convert annotations and <converter>, <convert> XML elements. A Converter is a user defined class that provides custom conversion routines in Java code. It must implement the AttributeConverter interface and be annotated with the @Converter annotation (or specified in XML). A Converter can be used in one of two ways. Normally it is specified on a mapping using the @Convert annotation or <convert> XML element. Another option, if converting a custom type, is to have the Converter applied to any mapped attribute that has that type. To define such as global converter the autoApply flag is added to the @Converter annotation. The @Convert disableConversion flag can be used to disable a global converter from being applied. The @Convert attributeName option can be used to override inherited or embeddable conversions.

Example Converter[edit | edit source]

@Entity
public class Employee {
    ...
    @Convert(converter=BooleanTFConverter.class)
    private Boolean isActive;
    ...
}

@Converter
public class BooleanTFConverter implements AttributeConverter<Boolean, String>{
    @Override
    public String convertToDatabaseColumn(Boolean value) {
        if (Boolean.TRUE.equals(value)) {
            return "T";
        } else {
            return "F";
        }
    }
    @Override
    public Boolean convertToEntityAttribute(String value) {
        return "T".equals(value);
    }
}

Example global Converter[edit | edit source]

@Entity
public class Employee {
    ...
    private Boolean isActive;
    ...
}

@Converter(autoApply=true)
public class BooleanTFConverter implements AttributeConverter<Boolean, String>{
    @Override
    public String convertToDatabaseColumn(Boolean value) {
        if (Boolean.TRUE.equals(value)) {
            return "T";
        } else {
            return "F";
        }
    }
    @Override
    public Boolean convertToEntityAttribute(String value) {
        return "T".equals(value);
    }
}

Conversion[edit | edit source]

Previous to JPA 2.1 there was no standard way to convert between a data-type and an object-type. One way to accomplish this was to translate the data through property get/set methods.

@Entity
public class Employee {
    ...
    private boolean isActive;
    ...
    @Transient
    public boolean getIsActive() {
        return isActive;
    }
    public void setIsActive(boolean isActive) {
        this.isActive = isActive;
    }
    @Basic
    private String getIsActiveValue() {
        if (isActive) {
            return "T";
        } else {
            return "F";
        }
    }
    private void setIsActiveValue(String isActive) {
        this.isActive = "T".equals(isActive);
    }
}

Also for translating date/times see, Temporals.

As well some JPA providers have special conversion support.

TopLink, EclipseLink : Support translation using the @Convert, @Converter, @ObjectTypeConverter and @TypeConverter annotations and XML.

Custom Types[edit | edit source]

JPA defines support for most common database types, however some databases and JDBC driver have additional types that may require additional support.

Some custom database types include:

  • TIMESTAMPTZ, TIMESTAMPLTZ (Oracle)
  • TIMESTAMP WITH TIMEZONE (Postgres)
  • XMLTYPE (Oracle)
  • XML (DB2)
  • NCHAR, NVARCHAR, NCLOB (Oracle)
  • Struct (STRUCT Oracle)
  • Array (VARRAY Oracle)
  • BINARY_INTEGER, DEC, INT, NATURAL, NATURALN, BOOLEAN (Oracle)
  • POSITIVE, POSITIVEN, SIGNTYPE, PLS_INTEGER (Oracle)
  • RECORD, TABLE (Oracle)
  • SDO_GEOMETRY (Oracle)
  • LOBs (Oracle thin driver)

To handle persistence to custom database types you may be able to use a Converter or special feature of your JPA provider. Otherwise you may need to mix raw JDBC code with your JPA objects. Some JPA providers provide custom support for many custom database types, some also provide custom hooks for adding your own JDBC code to support a custom database type.

TopLink, EclipseLink : Support several custom database types including, TIMESTAMPTZ, TIMESTAMPLTZ, XMLTYPE, NCHAR, NVARCHAR, NCLOB, object-relational Struct and Array types, PLSQL types, SDO_GEOMETRY and LOBs.