Mapping Java Entities for Persistence in Hibernate (Part 1)
Learn more about how to map Java entities for persistence in Hibernate.
Join the DZone community and get the full member experience.
Join For FreeIn this series of posts, we’ll explore how the domain classes in a Java application can be mapped to relational tables for persistence in a database using Hibernate. The goal is to summarize the techniques and best practices for correctly implementing the domain model of applications that need to load and store objects in an SQL data store.
To make examples easy to follow, our domain consists of simple entities representing books along with their authors, namely: a Book
class, an Author
class, and Publisher
for representing the publisher of books. In this first part, the focus will be on basic mapping for the Book
entity.
Declaring Entity Mappings
Before going through the examples, let’s review how mappings from object-oriented domain classes to relational tables are added to an application. There are two ways: (1) using annotations in Java code, and (2) using XML files using either JPA standard format or Hibernate hbm.xml
files. In all the following examples, we will use annotations defined in the JPA standard and Hibernate specific annotations for features beyond what the JPA standard includes. Also, note that the target Hibernate version is 5.4 (which also supports JDK 11), but most of the details apply to either Hibernate 4.x and 5.x releases.
Basic Entity Mapping
The starting point for mapping a class is the JPA annotation @javax.persistence.Entity
. It is required for enabling the persistence of the class. In addition, an identifier for the entity must be specified, mapping it to the primary key column using the @javax.persistence.Id
JPA annotation. The most basic mapping is shown below.
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Book {
@Id
private Long id;
...
}
How does the identifier property (the id
field) get its value? In this case, Hibernate expects you to set the value explicitly. In other words, if you try to persist a Book
instance by creating it with the default constructor and calling session.save()
, an exception will occur because the value of the identifier (and therefore the primary key column) is null (notice that id
is of type Long
, not the primitive long
, which would have worked because it defaults to 0):
session.save(new Book()); // throws IdentifierGenerationException: ids for
// this class must be manually assigned before
// calling save()
In this case, the id
value must be assigned either via a constructor or a setter method. Normally, however, you want to let Hibernate automatically generate the identifier value, for example, using an auto-incremented column in the target table. This can be done using the @GeneratedValue
annotation:
import javax.persistence.GeneratedValue;
@Entity
public class Book {
@Id
@GeneratedValue
protected Long id;
...
}
The next question is: how exactly does Hibernate generate the id value? The answer is: It depends on the database vendor, or more specifically, the org.hibernate.dialect.Dialect
subclass for the target database. Most of the time, it turns out to use either a sequence or an identity column as the strategy to generate the value. To be consistent from one database to another, it is best to specify the generation strategy explicitly. Hibernate provides several strategies. For example, enhanced-sequence
is a generator strategy that uses a sequence to get the next value, and if the database does not support sequences, it falls back to a table simulating a sequence. Standardized generator strategies are defined in the enum javax.persistence.GenerationType
. For example, to use the JPA standard sequence-based generator, we set the strategy to GenerationType.SEQUENCE
:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
protected Long id;
...
}
To use the more powerful Hibernate generation strategies and to gain control over generation parameters, we can leverage the Hibernate @org.hibernate.annotations.GenericGenerator
annotation:
@Entity
public class Book {
@Id
@GeneratedValue(generator = "idGenerator")
@org.hibernate.annotations.GenericGenerator(name = "idGenerator",
strategy = "enhanced-sequence",
parameters = {
@org.hibernate.annotations.Parameter(name = "initial_value",
value = "100")
})
protected Long id;
...
}
This causes Hibernate to use the sequence (or a table simulating it) to get the next id value, starting at value 100, i.e. the first entity instance that gets persisted would have id value 100, the next would have 101, etc.
More on Basic Entity Mapping
Excluding Columns From Persistence
By default, all fields in the annotated class get included in the persisted rows, along with the primary column mapped by the id field (actually to be specific, all fields whose types are either basic built-in Java types or embeddable types, or serializable, or fields that are mapped using associations; other fields will cause an error). Let’s say Book
has a title
field, then it will be automatically mapped by Hibernate:
@Entity
public class Book {
@Id
...
protected Long id;
protected String title; // No need for mapping annotation
... // getter(s) and setter(s)
}
Suppose we wanted to exclude title
from being persisted, we can annotate by @javax.persistence.Transient
or use the transient
modifier:
import javax.persistence.Transient;
@Entity
public class Book {
...
@Transient
protected String title;
... // getter(s) and setter(s)
}
Controlling the Column Mapping
Now, let’s suppose that we do want the title
field to be persisted, but we want to control its mapped column, for example, to make it NOT NULL
, or maybe to change the name of the column. To do this, we use the @Column
annotation:
import javax.persistence.Column;
@Entity
public class Book {
@Id
...
protected Long id;
// Note that TITLE is already the default column name
@Column(name = "TITLE", nullable = false)
protected String title;
...
}
Now, Hibernate would throw an exception if a Book
is persisted while having a null title
. Hibernate also adds a NOT NULL
constraint when generating the schema using its DDL generation tool (as configured by the setting hibernate.hbm2ddl.auto
).
Table Naming
Another thing we might want to control is the name of the table mapped by the class. This can be done using @Table
being placed on the class:
import javax.persistence.Table;
@Entity
@Table(name = "BOOK")
public class Book {
...
}
Entity Naming
The @Entity
annotation also has a name
element, but this controls the entity’s name, not the table name. The entity name is used queries executed using Hibernate’s supported query syntax. For example, to select a book given its title, a query would be:
Query<Book> query = session.createQuery("from Book where title = :title",
Book.class);
query.setParameter("title", "some title");
Book book = query.getSingleResult();
The entity name is by default the unqualified name of the class. If we have, for whatever reason, another Book
entity class in another package, then we would need to change the entity name to avoid any naming conflict in the query.
Mapping an Immutable Class
Some of the entity classes may be designed to be immutable. In this case, the annotation @org.hibernate.annotations.Immutable
can be used to tell Hibernate that it should ignore any updates to the entity. It helps improve performance by excluding the class from dirty checking during persistence operations.
@Entity
@org.hibernate.annotations.Immutable
public class Book {
...
}
Mapping Date/Time Fields
Fields of type java.util.Date
and java.util.Calendar
have a special mapping requirement in JPA. Such a field needs to be annotated with @javax.persistence.Temporal
specifying whether the temporal type is a date, a time, or a timestamp. Note, however, that Hibernate works without @Temporal
by defaulting to a temporal type of TIMESTAMP
.
Let’s say we want to add a new field containing the publishing date for a book. We probably want the date to contain only the year, the month, and the day, without any time component. This can be done using by setting the @Temporal
annotation as follows:
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
public class Book {
...
@Temporal(TemporalType.DATE)
protected Date publishingDate;
...
}
This should map it to a SQL DATE type. Note that the default value of publishingDate
is null. The user has to set it on the Book
instance before saving it. If the field was instead creationTime
representing the timestamp at which the object was inserted into the table, then we might want to have it auto-generated for us before insertion. To accomplish this, we can use the @org.hibernate.annotations.CreationTimestamp
, as shown below.
@Temporal(TemporalType.TIMESTAMP) // default in Hibernate
@CreationTimestamp
protected Date creationTime;
For an auto-generated timestamp upon an update, a similar @org.hibernate.annotations.UpdateTimestamp
can be used.
Mapping Embedded Types
Some classes have semantics that makes them be part of or embedded in an entity instance, without them having an identity that makes them independent. For example, let’s say we want to have a class BookMetadata
that contains some information about a book, e.g. a string containing the main topic. A Book
would have a reference to a BookMetadata
, but that instance does not have a separate identity or lifecycle. It is completely part of the Book
object in the sense of composition in object-oriented terms. BookMetadata
has essentially the same relationship as a String
or a Date
. We map it using an @Embeddable
annotation to mark it as such:
import javax.persistence.Embeddable;
@Embeddable
public class BookMetadata {
private String mainTopic;
private boolean coauthored;
... // constructors, getters, setters
}
@Entity
public class Book {
...
protected BookMetadata bookMetadata;
...
}
The important thing to realize on the mapped table side is that we now have one table called BOOK
containing columns mapped by the Book
class, plus the columns mapped by BookMetadata
. Here is the generated schema:
create table BOOK (
id bigint not null,
coauthored boolean not null,
mainTopic varchar(255),
publishingDate timestamp,
title varchar(255) not null,
primary key (id)
)
We may want to change the name of a column in the embeddable field. To do this, we need to override the mapping of the fields of BookMetadata
using the annotation @AttributeOverride
applied on the owning entity, as shown below:
import javax.persistence.AttributeOverride;
@Entity
public class Book {
...
@AttributeOverride(name = "mainTopic",
column = @Column(name = "TOPIC", length = 60, nullable = false))
@AttributeOverride(name = "coauthored",
column = @Column(name = "CO_AUTHORED"))
protected BookMetadata bookMetadata;
...
}
Note that we also changed the length of VARCHAR from 255 to 60 within the overridden @Column
mapping for mainTopic
, and made it not nullable. The resulting schema is then:
create table BOOK (
id bigint not null,
CO_AUTHORED boolean,
TOPIC varchar(60) not null,
publishingDate timestamp,
title varchar(255) not null,
primary key (id)
)
In the upcoming posts, we will explore more topics on entity mapping, including mapping collections, classes with inheritance, and more. Stay tuned...!
Published at DZone with permission of Mahmoud Anouti, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments