Key annotations you need to know when working with JPA and Hibernate

By Thorben Janssen

Mapping

When you start learning and using Hibernate and JPA, the number of annotations might be overwhelming. But as long as you rely on the defaults, you can implement your persistence layer using only a small subset of them.

After you have mastered the basic annotations, you can take a look at additional customization options. You can, for example, customize the join tables of many-to-many associations, use composite primary keys, or share a primary key value between 2 associated entities.

But please be careful with any mapping that tries to handle a significant difference between your table model and your domain model. Quite often, the simpler mappings are better than the complex ones. They provide better performance and are much easier to understand by all developers in your team.

You only need the more advanced mappings if you need to map a legacy database or use various kinds of performance optimizations. But especially when you are new to JPA and Hibernate, you should ignore these features and focus on the basic concepts.

So, let’s take a look at the most important annotations and their attributes. For each annotation, I will explain which attributes you really need and which ones you should better avoid.

And if you want to dive deeper into JPA and make sure you have a solid understanding of all the basic concepts, I recommend enrolling in my JPA for Beginners online course.

Define an Entity Class

JPA entities don’t need to implement any interface or extend a superclass. They are simple POJOs. But you still need to identify a class as an entity class, and you might want to adapt the default table mapping.

@Entity

The JPA specification requires the @Entity annotation. It identifies a class as an entity class.

@Entity
public class Author { ... }

You can use the name attribute of the @Entity annotation to define the name of the entity. It has to be unique for the persistence unit, and you use it to reference the entity in your JPQL queries.

@Table

By default, each entity class maps a database table with the same name in the default schema of your database. You can customize this mapping using the name, schema, and catalog attributes of the @Table annotation.

@Entity
@Table(name = "AUTHORS", schema = "STORE")
public class Author {

The name attribute enables you to change the name of the database table which your entity maps. The schema attribute specifies the name of the database schema in which the table is located. And the catalog attribute describes the name of the database catalog that stores the metadata information of the table.

The @Table annotation also defines 2 attributes that enable you to influence the generation of the database table. These are called indexes and uniqueConstraints. I don’t recommend to use them. External script and tools like Liquibase or Flyway are a much better option to create and update your database.

Basic Column Mappings

By default, all JPA implementations map each entity attribute to a database column with the same name and a compatible type. The following annotations enable you to perform basic customizations of these mappings. You can, for example, change the name of the column, adapt the type mapping, identify primary key attributes, and generate unique values for them.

@Column

Let’s start with the @Column annotation. It is an optional annotation that enables you to customize the mapping between the entity attribute and the database column.

@Entity
public class Book {

    @Column(name = "title", updatable = false, insertable = true)
    private String title;

    ...
}

You can use the name attribute to specify the name of the database column which the entity attribute map. The attributes updatable and insertable enable you to exclude the attribute from insert or update statements.

You should only use the table attribute if you map your entity to 2 database tables. In general, I don’t recommend to use this mapping. But you sometimes need it to work with a legacy database or as a temporary step during a complex refactoring.

All other attributes only affect the generated CREATE TABLE statement, and I don’t recommend to use them. These are:

  • The columnDefinition attribute that allows you to define an SQL fragment that’s used during table definition.
  • The length attribute, which defines the length of String-valued database column.
  • The attributes scale and precision, which specify the scale and precision of a decimal column.
  • The unique attribute that defines a unique constraint on the mapped column.

@Id

JPA and Hibernate require you to specify at least one primary key attribute for each entity. You can do that by annotating an attribute with the @Id annotation.

@Entity
public class Author {

    @Id
    private Long id;

    ...
}

@GeneratedValue

When we’re talking about primary keys, we also need to talk about sequences and auto-incremented database columns. These are the 2 most common database features to generate unique primary key values.

If you annotate your primary key attribute with the @GeneratedValue annotation, you can use a database sequence by setting the strategy attribute to GenerationType.SEQUENCE. Or, if you want to use an auto-incremented database column to generate your primary key values, you need to set the strategy to GenerationType.IDENTITY.

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    ...
}

The generator attribute of the @GeneratedValue annotation enables you to reference a custom generator. You can use it to customize a standard generator, e.g., to use a custom database sequence, or to implement your own generator.

I explain the primary key generation strategies and their performance impacts in more detail in How to generate primary keys with JPA and Hibernate.

@Enumerated

The @Enumerated annotation enables you to define how an enum attribute gets persisted in the database. By default, all JPA implementations map the ordinal value of the enum to a numeric database column.

As I explained in more detail in my guide on enum mappings, the ordinal makes it hard to add or remove values to the enum. The mapping as a String is more robust and much easier to read. You can activate this mapping by EnumType.STRING to the @Enumerated annotation.

@Entity
public class Author {

    @Enumerated(EnumType.STRING)
    private AuthorStatus status;

    ...
}

@Temporal

If you’re still using java.util.Date or java.util.Calendar as your attribute types, you need to annotate the attribute with @Temporal. Using this annotation, you can define if the attribute shall be mapped as an SQL DATE, TIME, or TIMESTAMP.

@Entity
public class Author {
	
    @Temporal(TemporalType.DATE)
    private Date dateOfBirth;

    ...
}

This mapping works really well, but I recommend using the classes of the Date and Time API instead. These classes are much easier to use in your business code, and they provide all the required mapping information. That means that they don’t require any annotations.

@Lob

In Java, there is almost no limit to the size of a String or a byte[]. But that’s not the case for relational databases. They provide specific data types for large objects. These are BLOB for binary large objects and CLOB for character large objects.

Using JPA’s @Lob annotation, you can map a BLOB to a byte[] and a CLOB to a String. Your persistence provider then fetches the whole BLOB or CLOB when it initializes the entity attribute.

@Entity
public class Book {
     
    @Lob
    private byte[] cover;
 
    ...
}

In addition to that, Hibernate also supports mappings to java.sql.Blob and java.sql.Clob. These are not as easy to use a byte[] or a String, but they can provide better performance. I explained that mapping in great detail in Mapping BLOBs and CLOBs with Hibernate and JPA.

Association Mappings

You can also map associations between your entities. In the table model, these are modeled as foreign key columns. These associations are mapped as attributes of the type of the associated entity or a Collection of associated entities, in your domain model.

In both cases, you need to describe the association mapping. You can do that using a @ManyToMany, @ManyToOne, @OneToMany, or @OneToOne annotation.

@ManyToMany

Many-to-many associations are very common in relational table models. A typical example is an association between books and authors.

In your domain model, you can map this association in a uni- or bidirectional way using attributes of type List, Set or Map, and a @ManyToMany annotations.

@Entity
@Table(name = "BOOKS")
public class Book {

    @ManyToMany
    private Set<Author> authors;

    ...
}

Here you can see a typical example of the owning side of the association. You can use it to model a unidirectional many-to-many association. Or you can use it as the owning side of a bidirectional mapping. In both cases, Hibernate uses an association table that contains foreign key columns that reference both ends of the association.

When you’re using this annotation, you should also be familiar with JPA’s FetchTypes. The fetch attribute of the @ManyToMany annotation allows you to define the FetchType that shall be used for this association. The FetchType defines when the persistence provider fetches the referenced entities from the database. By default, a many-to-many association uses the FetchType.LAZY. This tells your persistence provider to fetch the associated entities when you use them. That’s the most efficient approach, and you shouldn’t change it.

By setting the cascade attribute, you can also tell your persistence provider which entity operations it shall cascade to all associated entities. This can make working with graphs of entities much easier. But you should avoid CascadeType.REMOVE for all many-to-many associations. It removes much more data than you would expect.

If you want to model the association in a bidirectional way, you need to implement a similar mapping on the referenced entity. But this time, you also need to set the mappedBy attribute of the @ManyToMany annotation to the name of the attribute that owns the association. To your persistence provider, this identifies the mapping as a bidirectional one.

@Entity
public class Author {

    @ManyToMany(mappedBy = "authors")
    private Set<Book> books;

    ...
}

You use the same @ManyToMany annotation to define the referencing side of the association, as you use to specify the owning side of it. So, you can use the same cascade and fetch attributes, as I described before.

@ManyToOne and @OneToMany

Many-to-one and one-to-many associations represent the same association from 2 different perspectives. So, it’s no surprise that you can use them together to define a bidirectional association. You can also use each of them on their own to create a unidirectional many-to-one or one-to-many association. But you should avoid unidirectional one-to-many associations. Hibernate handles them very inefficient.

@ManyToOne

Let’s take a closer look at the @ManyToOne annotation. It defines the owning side of a bidirectional many-to-one/one-to-many association. You do that on the entity that maps the database table that contains the foreign key column.

@Entity
public class Book {

    @ManyToOne(fetch = FetchType.LAZY)
    private Publisher publisher;

    ...
}

When you’re using a @ManyToOne annotation, you should be familiar with its fetch and cascade attributes.

The fetch attribute enables you to define the FetchType that shall be used for this association. The default value is FetchType.LAZY, and you shouldn’t change it.

You can set the cascade attribute to define which operations on this entity shall get cascaded to all associated entities. That gets often used to cascade an operation from a parent to a child entity. So, it’s mostly used on a @OneToMany association, and I will show it in the next section.

You can also set the optional attribute to false to indicate that this association is mandatory.

@OneToMany

You can use the @OneToMany annotation to define the referencing side of a bidirectional many-to-one/one-to-many association. As explained before, you shouldn’t use it to model a unidirectional one-to-many association. Hibernate handles these associations very inefficiently.

Similar to the referencing side of a bidirectional many-to-many association, you can reference the name of the attribute that owns the association in the mappedBy attribute. That tells your persistence provider that this is the referencing side of a bidirectional association, and it reuses the association mapping defined by the owning side.

@Entity
public class Publisher {

    @OneToMany(mappedBy = "publisher", cascade = CascadeType.ALL)
    private Set<Book> books;

    ...
}

I already explained the fetch and cascade attributes for the @ManyToMany and @ManyToOne annotations. You can use them in the same way with the @OneToMany annotation.

In addition to these 2 attributes, you should also know the orphanRemoval attribute. If you set it to true, Hibernate removes an entity from the database when it gets removed from the association. That’s often used for parent-child associations in which the child can’t exist without its parent. A typical example would be the item of an order. The item can’t exist without the order. So, it makes sense to remove it as soon as the association to the order gets removed.

@OneToOne

One-to-one associations are only rarely used in relational table models. You can map them using a @OneToOne annotation.

Similar to the previously discussed association mapping, you can model a uni- or bidirectional one-to-one associations. The attribute that’s defined on the entity that maps the database table that contains the foreign key column owns the association.

@Entity
public class Manuscript {

    @OneToOne(fetch = FetchType.LAZY)
    private Book book;

    ...
}

The @OneToOne annotation supports the fetch, cascade, and optional attributes that I already explained in the previous sections.

And if you model it as a bidirectional association, you need to set the mappedBy attribute of the referencing side of the association to the attribute name that owns the association.

@Entity
public class Book {

    @OneToOne(mappedBy = "book")
    private Manuscript manuscript;

    ...
}

Conclusion

As you have seen, you only need a relatively small number of annotations to define your domain model. In most cases, you only need to annotate your entity class with @Entity and your primary key attribute with @Id and @GeneratedValue.

If the names of your entity class or one of its attributes don’t match the table or column names, you can adjust the mapping using a @Table or @Column annotation. You can also change the type mappings using an @Enumerated, @Temporal, or @Lob annotation.

One of the key features of any object-relational mapper is the handling of associations. With JPA and Hibernate, you can map one-to-one, one-to-many, many-to-one, and many-to-many associations in a uni- or bidirectional way. All association mappings require an additional annotation that describes the association mapping and that you can use to define the fetching and cascading behavior of it.


Tags

Mapping


About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting

Leave a Repl​​​​​y

Your email address will not be published. Required fields are marked

This site uses Akismet to reduce spam. Learn how your comment data is processed.

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}