Suppose you have wondered how we interact with relational databases without (directly) using SQL queries. In that case, the answer usually lies in Object-Relational Mapping (ORM), which will do the job of converting Java objects and statements to database tables and related queries.

Hibernate comes into this story as the ORM framework that holds a standard implementation of the Java Persistence API (JPA) specification. The Java Persistence API (JPA) defines how data will persist in Java applications. Mapping Java objects to the database tables is done through JPA, consisting of interfaces that Hibernate implements, annotations that Hibernate uses, and Hibernate-specific annotations.

In the additional text, we will get familiar with JPA annotations and Hibernate annotations.

Java Object -> Database Table

Note: In further examples, getters and setters will be excluded for readability.

@Entity 

Entities in JPA are POJOs representing data that can be persisted to the database, where the entity equals a table stored in the database. Every instance of an entity is a row in a table.

Let’s say we want to persist this POJO in the database:

public class Atlanter {...}

We need to define an entity so JPA can be aware of it. This is done through @Entity annotation, which needs to be specified at the class level.

@Entity
public class Atlanter {...}

@Table 

In cases where the table in the database and the entity’s name aren’t the same, we can define a table name using @Table annotation.

@Entity

@Table(name=“ATLANTER”)

public class Atlanter {...}

@Id 

This annotation will define a primary key, and along with the @GeneratedValue annotation, we can generate identifiers.

@GeneratedValue will generate values for the specific column and help create primary keys. It uses different strategies in generating values such as: 

  • GenerationType.AUTO (default) – determines values based on type.
  • GenerationType.IDENTITY – values based on the identity column in the database.
  • GenerationType.SEQUENCE – uses sequences if the database supports them.
  • GenerationType.TABLE – uses an underlying database table that holds segments of identifier generation values.
@Entity
@Table(name=“ATLANTER”)
public class Atlanter {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
}

@Column 

This annotation allows us to specify column-related details. For example:

@Entity
@Table(name=“ATLANTER”)
public class Atlanter {
    @Id
    @GeneratedValue
    private Long id;

    @Column(name=“ATLANTER_NAME”, length=50, nullable=false, unique=false)
    private String name;
}

@Transient 

This annotation allows us not to persist a field to the table if that suits our needs. For example, we can calculate Atlanters’ age from the date of birth.

@Entity
@Table(name=“ATLANTER”)
public class Atlanter {
    @Id
    @GeneratedValue
    private Long id;

    @Column(name=“ATLANTER_NAME”, length=50, nullable=false, unique=false)
    private String name;

    @Transient
    private Integer age;
}

@Enumerated 

This annotation will serve us when we want to persist Java enum types. It will specify if we’re going to persist the enum by name or by ordinal (default):

public enum Gender {...}

@Entity
@Table(name=“ATLANTER”)
public class Atlanter {
    @Id
    @GeneratedValue
    private Long id;

    @Column(name=“ATLANTER_NAME”, length=50, nullable=false, unique=false)
    private String name;

    @Transient
    private Integer age;

    @Enumerated(EnumType.STRING)
    private Gender gender;
}

These are some of the most commonly used JPA annotations to define and customize the entity.

JPA Annotations for Mapping Between Tables

Suppose we have to store an email address for each Atlanter where each Atlanter will only have one email, and one email will have only one Atlanter tied to it. This is called a “one-to-one” relationship.

We are going to place @OneToOne annotation on the related entity. Also, we will place @JoinColumn annotation and configure the column’s name in the Atlanter table that maps to the primary key in the Email table. This annotation is put on the owning side* of the relationship.

Whoever owns the foreign key column gets this annotation.

@Entity
@Table(name = “ATLANTER”)
public class Atlanter {
    @Id
    @GeneratedValue
    @Column(name = “id”)
    private Long id;
    
    @OneToOne
    @JoinColumn(name = “email_id”, referencedColumnName = “id”)
    private Email email;
}

*Owning side The owning side of the relation tracked by Hibernate is the side of the relation that owns the foreign key in the database.

Therefore, we need to place @OneToOne annotation in the Email entity column too, where this side of the relationship is called the non-owning side.

@Entity
@Table(name = “email”)
public class Email {
    @Id
    @GeneratedValue
    @Column(name = “id”)
    private Long id;
    
    @OneToOne(mappedBy = “email”)
    private Atlanter atlanter;
}

The relationship we would use to describe one department having many Atlanters is a “one-to-many” relationship with @OneToMany annotation.

@Entity
@Table(name = “DEPARTMENT”)
public class Department {
    @Id
    @GeneratedValue
    @Column(name = “id”)
    private Long id;
    
    @OneToMany(mappedBy = “department”)
    private Set<Atlanter> atlanters;
}

We could also add @ManyToOne annotation on the Department entity, making this a bidirectional relationship, meaning we are able to access departments from the Atlanter entity and vice versa.

@Entity
@Table(name=“ATLANTER”)
public class Atlanter {
    @Id
    @GeneratedValue
    @Column(name = “id”)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = “dept_id”)
    private Department department;
}

Sometimes, Atlanters can work on many projects, and a project can have many Atlanters working on it; we would describe that relationship with @ManyToMany annotation.

Here we will use @JoinTable annotation, which will use a separate table to hold the relationship between Atlanter and Project. This time neither Atlanter nor Project contains any foreign key because there is a separate table (e.g., Atlanter_Project) that will hold the association between Atlanter and Project, but if we choose not to define @JoinTable, it will be generated for us; hence it would be better if we explicitly defined it ourselves.

@Entity
@Table(name = “ATLANTER”)
public class Atlanter {
    …
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(
name = “Atlanter_Project” 
joinColumns = @JoinColumn(name = “atlanter_id”) 
inverseJoinColumns = @JoinColumn(name = “project_id”)
)
    private Set<Project> projects;
}

@Entity
@Table(name=“PROJECT”)
public class Project {
    …
    @ManyToMany(mappedBy = “projects”)
    private Set<Atlanter> atlanters;
}

@JoinColumn vs. mappedBy explained

@JoinColumn in combination with @OneToOne mapping is indicating that a given column in the owner entity refers to a primary key in the reference entity.

When using @OneToMany mapping, we can use the mappedBy attribute to indicate that another entity owns the given column.

There will be situations where we want to join multiple columns, and for that, we will use @JoinColumns annotation.

@Entity
public class AtlantOffice {
    @ManyToOne
    @JoinColumns ({
        @JoinColumn(name = “ADDR_ID”),
        @JoinColumn(name = “ADDR_ZIP)
    })
    private Address address;
}

This will create two foreign keys pointing to ADDR_ID and ADDR_ZIP columns in the Address entity.

Usually, in one-to-many/many-to-one relationships, the owning side is defined as the “many” side of the relationship, and that is typically the side that owns the foreign key. @JoinColumn annotation defines the actual physical mapping on the owning side. 

Fetch Types and Cascade

By default, @ManyToMany and @OneToMany relationships use FetchType.LAZY, which will fetch the associated entities when we use them. This is the most efficient approach, but we can also set FetchType.EAGER, which basically means “Fetch it right away so you will have it when you need it.” This type is the default for “…to-one” relationships.

By setting the cascade attribute, we define which entity operations will be cascaded to all associated entities.

Some of the cascade attributes are:

CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE etc.

Entity Graphs

The use of FetchType.LAZY and FetchType.EAGER won’t allow switching between two strategies at runtime, as they are static. 

In JPA 2.1, we can use the EntityGraph feature that will allow us to define a template by grouping the related persistence fields that we want to retrieve, and we will be able to switch graph types at runtime. Entity Graph can be used as a fetch or load graph, where while using fetch, only the attributes that are specified in the entity graph will be treated as FetchType.EAGER, while all the others will be treated as lazy. On the other hand, if a load graph is used, all attributes that are not specified in the entity graph will keep their default fetch type.

The definition of a named entity graph is done with @NamedEntityGraph annotation at the entity, where we can define a name and a list of attributes.

@Entity
@Table(name = “ATLANTER”)
@NamedEntityGraph(name = "graph.Department.atlanters”, 
      attributeNodes = @NamedAttributeNode(“atlanters”))
public class Atlanter { ... }

Now, we can use this entity graph in a query.

EntityGraph graph = this.em.getEntityGraph("graph.Department.atlanters”);
 
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
 
return this.em.find(Department.class, departmentId, hints);

Conclusion

Looking at this article, it can be clear that we need only a few annotations to define our model. Using @Entity@Id, and @GeneratedValue, we can annotate our entity class, along with @Table and @Column annotation where we can define table or column name or change the type mappings using @Enumerated.

Of course, the key feature of any ORM is the handling of relationships, whereas, with JPA and Hibernate, we can use “one-to-one,” “one-to-many,” “many-to-one” and “many-to-many” relationships in a uni or bidirectional way. In all of these mappings, we can use additional attributes and annotations to describe this relationship and define its fetching and cascading behavior. We can also choose to load or not the related association by using entity graphs.


“JPA annotations in Hibernate” Tech Bite was brought to you by Anisa Ćirić, Software Engineer at Atlantbh.

Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.

Decorator pattern
Software DevelopmentTech Bites
January 23, 2023

Decorator pattern

Design patterns are typical solutions to common problems in software design. Each pattern is like a blueprint that you can customize to solve a particular design problem in your code. A decorator pattern allows users to add new functionality to an existing object without altering its structure. This design pattern comes under…

Leave a Reply