Hibernate/JPA Persistence Annotations

In this hibernate tutorial, we will know the overview of all important JPA annotations that we use in creating JPA entities. Hibernate version 6.0 moves from Java Persistence as defined by the Java EE specs to Jakarta Persistence as defined by the Jakarta EE spec so we need to import all the annotations from jakarta.persistence package.

1. Setting Up the Prerequisites

Start with importing the required dependencies. We need hibernate-core as a mandatory dependency.

<dependency>
      <groupId>org.hibernate.orm</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>6.0.0.Final</version>
</dependency>

This tutorial first defines a POJO “EmployeeEntity” and some fields, respective getter and setter methods.

As we learn the new annotations, we will apply these annotations to this EmployeeEntity and then we will understand what that specific annotation means.

public class EmployeeEntity implements Serializable
{ 
   private Integer           employeeId;
   private String            firstName;
   private String            lastName;
}

2. Most used JPA Annotations

2.1. @Entity

This shall be the first step in marking the POJO as a JPA entity. To do this, we need to apply @Entity annotation as follows:

@Entity
public class EmployeeEntity implements Serializable
{ 
  //...
}

The @Entity annotation marks this class as an entity bean, so the class must have a no-argument constructor that is visible with at least protected scope (JPA specific).

Hibernate supports package scope as the minimum, but we lose portability to other JPA implementations because they might be allowing only protected level scope.

Ideally, we should make this constructor public, which makes it highly compatible with other specifications as well. There are some more rules such as POJO class must not be final, and it must not be abstract as well.

2.2. Primary Keys with @Id and @GeneratedValue

Each entity bean has to have a primary key, which you annotate on the class with the @Id annotation. Typically, the primary key will be a single field, though it can also be a composite of multiple fields which we will see in later sections.

The placement of the @Id annotation determines the default access strategy that Hibernate will use for the mapping. If the annotation is applied to a field as shown below, then “field access” will be used.

@Id
private Integer employeeId;

If the @Id annotation is applied to the accessor for the field then property access will be used.

Property access means that Hibernate will call the mutator/setter instead of actually setting the field directly, what it does in case of field access. This gives the flexibility to alter the value of the actual value set in id field if needed. Additionally, you can apply extra logic to the setting of ‘id‘ field in mutator for other fields as well.

@Id
public Integer getEmployeeId()
{
  return employeeId;
}

By default, the @Id annotation will not create a primary key generation strategy, which means that you, as the code’s author, need to determine what valid primary keys are, by setting them explicitly by calling setter methods. OR you can use @GeneratedValue annotation.

@GeneratedValue annotation takes a pair of attributes: strategy and generator as below:

@Id
@GeneratedValue (strategy = GenerationType.SEQUENCE)
private Integer employeeId;

//OR a more complex use can be

@Id
@GeneratedValue(strategy=GenerationType.TABLE , generator="employee_generator")
@TableGenerator(name="employee_generator",
			   table="pk_table",
			   pkColumnName="name",
			   valueColumnName="value",
			   allocationSize=100)
private Integer employeeId;

The strategy attribute must be a value from the javax.persistence.GeneratorType enumeration. If you do not specify a generator type, the default is AUTO. There are four different types of primary key generators on GeneratorType, as follows:

  1. AUTO: Hibernate decides which generator type to use, based on the database’s support for primary key generation.
  2. IDENTITY: The database is responsible for determining and assigning the next primary key.
  3. SEQUENCE: Some databases support a SEQUENCE column type. It uses @SequenceGenerator.
  4. TABLE: This type keeps a separate table with the primary key values. It uses @TableGenerator.

The generator attribute allows the use of a custom generation mechanism shown in the above code example.

2.3. Generating Primary Key Values with @SequenceGenerator

A sequence is a database object that can be used as a source of primary key values. It is similar to the use of an identity column type, except that a sequence is independent of any particular table and can therefore be used by multiple tables.

To declare the specific sequence object to use and its properties, you must include the @SequenceGenerator annotation on the annotated field. Here’s an example:

@Id
@SequenceGenerator(name="seq1",sequenceName="HIB_SEQ")
@GeneratedValue(strategy=SEQUENCE,generator="seq1")
private Integer employeeId;

Here, a sequence-generation annotation named seq1 has been declared. This refers to the database sequence object called HIB_SEQ. The name seq1 is then referenced as the generator attribute of the @GeneratedValue annotation.

Only the sequence generator name is mandatory; the other attributes will take sensible default values, but you should provide an explicit value for the sequenceName attribute as a matter of good practice anyway. If not specified, the sequenceName value to be used is selected by the persistence provider.

2.4. Generating Primary Key Values with @TableGenerator

The @TableGenerator annotation is used in a very similar way to the @SequenceGenerator annotation, but because @TableGenerator manipulates a standard database table to obtain its primary key values, instead of using a vendor-specific sequence object, it is guaranteed to be portable between database platforms.

For optimal portability and optimal performance, you should not specify the use of a table generator, but instead use the @GeneratorValue(strategy=GeneratorType.AUTO) configuration, which allows the persistence provider to select the most appropriate strategy for the database in use.

As with the sequence generator, the name attributes of @TableGenerator are mandatory and the other attributes are optional, with the table details being selected by the persistence provider. Let’s look at the example again.

@Id
@GeneratedValue(strategy=GenerationType.TABLE , generator="employee_generator")
@TableGenerator(name="employee_generator",
			   table="pk_table",
			   pkColumnName="name",
			   valueColumnName="value",
			   allocationSize=100)
private Integer employeeId;

The optional attributes are as follows:

  • allocationSize: Allows the number of primary keys set aside at one time to be tuned for performance.
  • catalog: Allows the catalog that the table resides within to be specified.
  • initialValue: Allows the starting primary key value to be specified.
  • pkColumnName: Allows the primary key column of the table to be identified. The table can contain the details necessary for generating primary key values for multiple entities.
  • pkColumnValue: Allows the primary key for the row containing the primary key generation information to be identified.
  • schema: Allows the schema that the table resides within to be specified.
  • table: The name of the table containing the primary key values.
  • uniqueConstraints: Allows additional constraints to be applied to the table for schema generation.
  • valueColumnName: Allows the column containing the primary key generation information for the current entity to be identified.

Because the table can be used to contain the primary key values for a variety of entries, it is likely to have a single row for each of the entities using it. It therefore needs its own primary key (pkColumnName), as well as a column containing the next primary key value to be used (pkColumnValue) for any of the entities obtaining their primary keys from it.

2.5. Compound Primary Keys with @Id, @IdClass, or @EmbeddedId

While the use of single-column surrogate keys is advantageous for various reasons, you may sometimes be forced to work with business keys. When these are contained in a single column, you can use @Id without specifying a generation strategy forcing the user to assign a primary key value before the entity can be persisted.

But in case of multi-column primary key, You must create a class to represent this primary key. It will not require a primary key of its own, of course, but it must be a public class, must have a default constructor, must be serializable, and must implement hashCode() and equals() methods to allow the Hibernate code to test for primary key collisions.

Your three strategies for using this primary key class once it has been created are as follows:

  1. Mark it as @Embeddable and add to your entity class a normal property for it, marked with @Id.
  2.  Add to your entity class a normal property for it, marked with @EmbeddableId.
  3. Add properties to your entity class for all of its fields, mark them with @Id, and mark your entity class with @IdClass, supplying the class of your primary key class.

The use of @Id with a class marked as @Embeddable is the most natural approach. The @Embeddable tag can be used for non-primary key embeddable values anyway. It allows you to treat the compound primary key as a single property, and it permits the reuse of the @Embeddable class in other tables.

One thing worth pointing out: the embedded primary key classes must be serializable.

2.6. Database Table Mapping with @Table and @SecondaryTable

By default, table names are derived from the entity names. Therefore, given a class Employee with a simple @Entity annotation, the table name would be “employee”, adjusted for the database’s configuration. If the entity name is changed (by providing a different name in the @Entity annotation, such as @Entity(“EMP_MASTER”)), the new name will be used for the table name.

The table name can be customized further, and other database-related attributes can be configured via the @Table annotation. This annotation allows you to specify many of the details of the table that will be used to persist the entity in the database.

As already pointed out, if you omit the annotation, Hibernate will default to using the class name for the table name, so you need only provide this annotation if you want to override that behavior. The @Table annotation provides four attributes, allowing you to override the name of the table, its catalog, and its schema, and to enforce unique constraints on columns in the table.

Typically, you would only provide a substitute table name thus: @Table(name="ORDER_HISTORY"). The unique constraints will be applied if the database schema is generated from the annotated classes, and will supplement any column-specific constraints. They are not otherwise enforced.

The @SecondaryTable annotation provides a way to model an entity bean that is persisted across several different database tables. Here, in addition to providing an @Table annotation for the primary database table, your entity bean can have an @SecondaryTable annotation, or an @SecondaryTables annotation in turn containing zero or more @SecondaryTable annotations.

The @SecondaryTable annotation takes the same basic attributes as the @Table annotation, with the addition of the join attribute. The join attribute defines the join column for the primary database table. It accepts an array of javax.persistence.PrimaryKeyJoinColumn objects. If you omit the join attribute, then it will be assumed that the tables are joined on identically named primary key columns.

When an attribute in the entity is drawn from the secondary table, it must be marked with the @Column annotation, with a table attribute identifying the appropriate table.

@Entity
@Table(name = "employee")
@SecondaryTable(name = "employee_details")
public class EmployeeEntity implements Serializable
{
   @Id
   @GeneratedValue (strategy = GenerationType.SEQUENCE)
   private Integer employeeId;
   private String  firstName;
   private String  lastName;

   @Column(table = "employee_details")
   public String address;
}

Columns in the primary or secondary tables can be marked as having unique values within their tables by adding one or more appropriate @UniqueConstraint annotations to @Table or @SecondaryTable’s uniqueConstraints attribute. Alternatively, You may also set uniqueness at the field level with the unique attribute on the @Column attribute.

@Entity
@Table(
      name="employee",
      uniqueConstraints={@UniqueConstraint(columnNames="firstName")}
      )
@SecondaryTable(name = "employee_details")
public class EmployeeEntity implements Serializable{

}

2.7. Persisting Basic Types with @Basic

By default, properties and instance variables in your POJO are persistent; Hibernate will store their values for you. The simplest mappings are therefore for the “basic” types. These include primitives, primitive wrappers, arrays of primitives or wrappers, enumerations, and any types that implement Serializable but are not themselves mapped entities.

These are all mapped implicitly—no annotation is needed. By default, such fields are mapped to a single column, and eager fetching is used to retrieve them (i.e., when the entity is retrieved from the database, all the basic fields and properties are retrieved). Also, when the field or property is not a primitive, it can be stored and retrieved as a null value.

This default behavior can be overridden by applying the @Basic annotation to the appropriate class member. The annotation takes two optional attributes, and is itself entirely optional. The first attribute is named optional and takes a boolean. Defaulting to true, this can be set to false to provide a hint to schema generation that the associated column should be created NOT NULL. The second is named fetch and takes a member of the enumeration FetchType. This is EAGER by default, but can be set to LAZY to permit loading on access of the value.

@Basic (fetch = FetchType.LAZY, optional = false)
private String  firstName;

The use of lazy loading is unlikely to be valuable, except when large serializable objects have been mapped as basic types (rather than given entity mappings of their own) and retrieval time may become significant. While the (default) EAGER value must be honored, the LAZY flag is considered to be a hint, and can be ignored by the persistence engine.

The @Basic attribute is usually omitted, with the @Column attribute being used where the @Basic annotation’s optional attribute might otherwise be used to provide the NOT NULL behavior.

2.8. Omitting Persistence with @Transient

Some fields, such as calculated values, may be used at run time only, and they should be discarded from objects as they are persisted into the database. The JPA specification provides the @Transient annotation for these transient fields. The @Transient annotation does not have any attributes—you just add it to the instance variable or the getter method as appropriate for the entity bean’s property access strategy.

The @Transient annotation highlights one of the more important differences between using annotations with Hibernate and using XML mapping documents. With annotations, Hibernate will default to persisting all of the fields on a mapped object. When using XML mapping documents, Hibernate requires you to tell it explicitly which fields will be persisted.

For example, if our EmployeeEntity has two additional fields “age” and “dateOfBirth” then you would like to store dateOfBirth in database, but you would like to calculate age in runtime based on value of dateOfBirth. So, ‘age’ field must be marked as transient.

@Transient
private Integer age;

2.9. Mapping Properties and Fields with @Column

The @Column annotation is used to specify the details of the column to which a field or property will be mapped. Some of the details are schema related, and therefore apply only if the schema is generated from the annotated files. Others apply and are enforced at run time by Hibernate (or the JPA 2 persistence engine). It is optional, with an appropriate set of default behaviors, but is often useful when overriding default behavior, or when you need to fit your object model into a preexisting schema.

The following attributes commonly being overridden:

  1. name : permits the name of the column to be explicitly specified—by default, this would be the name of the property.
  2. length : permits the size of the column used to map a value (particularly a String value) to be explicitly defined. The column size defaults to 255, which might otherwise result in truncated String data, for example.
  3. nullable : permits the column to be marked NOT NULL when the schema is generated. The default is that fields should be permitted to be null; however, it is common to override this when a field is, or ought to be, mandatory.
  4. unique : permits the column to be marked as containing only unique values. This defaults to false, but commonly would be set for a value that might not be a primary key but would still cause problems if duplicated (such as username).
@Column(name="FNAME",length=100,nullable=false)
private String  firstName;

There are some more attributes which are rather less used in real life projects. These are table, insertable, updatable, columnDefinition, precision and scale. I will leave you to explore them in detail.

3. Entity Association Annotations

I have already covered the modeling related concepts in separate detailed posts. Please read more about them in these linked articles, as having duplicate information here does not make sense.

3.1. Mapping Inheritance Hierarchies

Entities are not always associated with other entities as attributes; sometimes they are related using normal OOPs inheritance rules. Hibernate allows you to honor such relationships using @Inheritance annotation.

The JPA 2 standard and Hibernate both support three approaches to mapping inheritance hierarchies into the database. These are as follows:

  1. Single table (SINGLE_TABLE): One table for each class hierarchy
  2. Joined (JOINED): One table for each subclass (including interfaces and abstract classes)
  3. Table-per-class (TABLE_PER_CLASS): One table for each concrete class implementation

Persistent entities that are related by inheritance must be marked up with the @Inheritance annotation. This takes a single strategy attribute, which is set to one of three javax.persistence.InheritanceType enumeration values corresponding to these approaches (i.e. SINGLE_TABLE, JOINED or TABLE_PER_CLASS).

Lets discuss them in some detail.

3.1. Single Table

The single-table approach manages one database table for the main superclass and all its subtypes. There are columns for each mapped field or property of the superclass, and for each distinct field or property of the derived types. When following this strategy, you will need to ensure that columns are appropriately renamed when any field or property names collide in the hierarchy.

To determine the appropriate type to instantiate when retrieving entities from the database, a @DiscriminatorColumn annotation should be provided in the root (and only in the root) of the persistent hierarchy.

Let’s look at a quick example for now. I am leaving you to read more on this in official hibernate documentation. I will cover them in detail in some later post.

//The Root of the Inheritance Hierarchy Mapped with the SINGLE_TABLE Strategy

@Entity
@Inheritance(strategy = SINGLE_TABLE)
@DiscriminatorColumn(
    name="DISCRIMINATOR",
    discriminatorType=INTEGER
)
@DiscriminatorValue("1")
public class Book {
...
}

//A Derived Entity in the Inheritance Hierarchy
@Entity
@DiscriminatorValue("2")
public class ComputerBook extends Book {
...
}

3.2. Joined Table

An alternative to the monolithic single-table approach is the otherwise similar joined-table approach. Here a discriminator column is used, but the fields of the various derived types are stored in distinct tables.

@Entity
@Inheritance(strategy = JOINED)
@DiscriminatorColumn
    name="DISCRIMINATOR"
)
public class Book {
...
}

3.3. Table per Concrete Class

Finally, there is the table-per-class approach, in which all of the fields of each type in the inheritance hierarchy are stored in distinct tables. Because of the close correspondence between the entity and its table, the @DiscriminatorColumn annotation is not applicable to this inheritance strategy.

@Entity
@Inheritance(strategy = TABLE_PER_CLASS)
public class Book {
...
}

4. Other JPA 2 Persistence Annotations

Although we have now covered most of the core JPA 2 persistence annotations, there are a few others that you will encounter fairly frequently. We cover some of these in passing in the following sections.

4.1. Temporal Data with @Temporal

Fields or properties of an entity that have java.util.Date or java.util.Calendar types represent temporal data. By default, these will be stored in a column with the TIMESTAMP data type, but this default behavior can be overridden with the @Temporal annotation.

The annotation accepts a single value attribute from the javax.persistence.TemporalType enumeration. This offers three possible values: DATE, TIME, and TIMESTAMP. These correspond, respectively, to java.sql.Date, java.sql.Time, and java.sql.Timestamp. The table column is given the appropriate data type at schema generation time.

To map Java 8 date time classes to SQL types in linked article.

@Temporal(TemporalType.TIME)
java.util.Date startingTime;

4.2. Element Collections with @ElementCollection

In addition to mapping collections using one-to-many mappings, JPA 2 introduced an @ElementCollection annotation for mapping collections of basic or embeddable classes. You can use the @ElementCollection annotation to simplify your mappings.

@ElementCollection
List<String> passwordHints;

There are two attributes on the @ElementCollection annotation: targetClass and fetch. The targetClass attribute tells Hibernate which class is stored in the collection. If you use generics on your collection, you do not need to specify targetClass because Hibernate will infer the correct class. The fetch attribute takes a member of the enumeration, FetchType. This is EAGER by default, but can be set to LAZY to permit loading when the value is accessed.

4.3. Large Objects with @Lob

A persistent property or field can be marked for persistence as a database-supported large object type by applying the @Lob annotation.

The annotation takes no attributes, but the underlying large object type to be used will be inferred from the type of the field or parameter. String- and character-based types will be stored in an appropriate character-based type i.e. CLOB. All other objects will be stored in a BLOB.

@Lob
String content; // a very long article

The @Lob annotation can be used in combination with the @Basic or the @ElementCollection annotation.

4.4. Mapped Superclasses with @MappedSuperclass

A special case of inheritance occurs when the root of the hierarchy is not itself a persistent entity, but various classes derived from it are. Such a class can be abstract or concrete. The @MappedSuperclass annotation allows you to take advantage of this circumstance.

The class marked with @MappedSuperclass is not an entity, and is not query-able (it cannot be passed to methods that expect an entity in the Session or EntityManager objects). It cannot be the target of an association.

The mapping information for the columns of the superclass will be stored in the same table as the details of the derived class.

4.5. Ordering Collections with @OrderColumn

While @OrderBy allows data to be ordered once it has been retrieved from the database, JPA 2 also provides an annotation that allows the ordering of appropriate collection types (e.g., List) to be maintained in the database; it does so by maintaining an order column to represent that order. Here’s an example:

@OneToMany
@OrderColumn(
   name="employeeNumber"
)
List<Employee> employees;

Here, we are declaring that an employeeNumber column will maintain a value, starting at 0 and incrementing as each entry is added to the list. The default starting value can be overridden by the base attribute. By default, the column can contain null (unordered) values. The nullability can be overridden by setting the nullable attribute to false.

By default, when the schema is generated from the annotations, the column is assumed to be an integer type; however, this can be overridden by supplying a columnDefinition attribute specifying a different column definition string.

5. Named Queries (HQL or JPQL)

5.1. @NamedQuery and @NamedQueries

@NamedQuery and @NamedQueries allow one or more Hibernate Query Language or Java Persistence Query Language (JPQL) queries to be associated with an entity. The required attributes are as follows:

  1. name is the name by which the query is retrieved.
  2. query is the JPQL (or HQL) query associated with the name.

Take example of below “Author” entity.

@Entity
@NamedQuery(
        name="findAuthorsByName",
        query="from Author where name = :author"
)
public class Author {
...
}

The query would retrieve Author entities by name, so it is natural to associate it with that entity; however, there is no actual requirement that a named query be associated in this way with the entity that it concerns.

You do not need to directly associate the query with the entity against which it is declared, but it is normal to do so. If a query has no natural association with any of the entity declarations, it is possible to make the @NamedQuery annotation at the package level.

5.2. @NamedNativeQuery and @NamedNativeQueries

@NamedNativeQuery lets you write a named SQL query, while @NamedQuery lets you write a named HQL query (or JPQL).

In general, you should prefer to write HQL queries because then you can let Hibernate handle the intricacies of converting the HQL into the various SQL dialects. This will make your job much simpler when you choose to switch DBMS providers.

@NamedQueries({
   @NamedQuery(name="get-emp-by-name",query="FROM EmployeeBean WHERE fName=:fName")
})

//Equivalent NamedNativeQuery

@NamedNativeQueries(
	{
		@NamedNativeQuery(
			name="get-emp-by-name-native",
			query="SELECT * FROM Employees WHERE firstName=:fName",
			resultClass=EmployeeEntity.class)
	}
)

That’s all about this limited tutorial covering most important JPA 2 persistence annotations in short. I will be covering them in detail in later tutorials.

Happy Learning !!

Comments

Subscribe
Notify of
guest
9 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode