Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Mapping Java Entities for Persistence With Hibernate (Part 2)

DZone 's Guide to

Mapping Java Entities for Persistence With Hibernate (Part 2)

Learn more about mapping Java entities for persistence with Hibernate.

· Java Zone ·
Free Resource

Java hibernate article

Learn more about mapping Java entities for persistence with Hibernate.

In the previous post on mapping Java domain model classes using Hibernate and JPA, we visited some basic entity mapping topics, such as mapping identifiers, columns, and embedded types. In this part, we’ll review how to map collections in entities as well as basic entity associations.

You may also like: Java Persistence Done Right

Persisting Collections

JDK collections are heavily used in any complex enterprise application because they are naturally required to model the business domain. For example, in our Book entity, we may want to define a set of tags that describe the genre of the book. A Set<String> would be suitable because we don’t care about the order of these tags and there shouldn’t be duplicates:

import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.JoinColumn;

@Entity
public class Book {

    @ElementCollection
    @CollectionTable(name = "CONTENT_TAG",
                 joinColumns = @JoinColumn(name = "BOOK_ID"))
    @Column(name = "TAG", nullable = false)
    protected Set<String> contentTags = new HashSet<>();

    ...
}


It is important to remember to initialize the set within the declaration statement to avoid any null references. The declaration type also has to be the interface Set, not the implementation HashSet. The @ElementCollection is required to map this collection. The two other annotations specify metadata about the table that will store the collection. @CollectionTable specifies a name for the table (CONTENT_TAG) that will store the tags, along with a foreign key column BOOK_ID via the @JoinColumn in the collection table. Finally, the @Column defines the name of the column storing the actual tag strings, and also marks it as non-nullable.

If we enable logging of the schema creation, Hibernate will issue the following DDL when creating the collection table:

create table CONTENT_TAG (
   BOOK_ID bigint not null,
   TAG varchar(255) not null,
   primary key (BOOK_ID, TAG)
)

alter table CONTENT_TAG 
   add constraint FKitxsi0kpxsoexvnjlnlxmgci7 
   foreign key (BOOK_ID) 
   references Book


In other words, a new table CONTENT_TAG is created, which references the Book table via a foreign key BOOK_ID and contains a column TAG to store an element in the collection.

There are other types of collections that an entity may need to have such as a list or a map, but the way these are mapped is similar to the above (consult the documentation of Hibernate for more details). The differences are in how to specify the ordering in the case of ordered collections like a list, or how to map the key column in the case of a map.

Basic Entity Associations

So far, we mainly have one entity that is mapped, the Book entity. Applications usually have many more entities, where some are associated with others. For example, every book should have an author and a publisher, and these warrant their own entities in our model: an Author entity and a Publisher entity. Let’s define them:

@Entity
public class Author {

    @Id
    @GeneratedValue(generator = "idGenerator")
    @GenericGenerator(name = "idGenerator", strategy = "enhanced-sequence", parameters = {
             @Parameter(name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "BookSequence"),
             @Parameter(name = SequenceStyleGenerator.INITIAL_PARAM, value = "100") })
    protected Long id;

    @Column(nullable = false)
    protected String name;

    @Temporal(TemporalType.DATE)
    protected Date birthDay;

    @Column(length = 30, nullable = false)
    protected String country;

    ...
}

@Entity
public class Publisher {

    @Id
    @GeneratedValue(generator = "idGenerator")
    @GenericGenerator(name = "idGenerator", strategy = "enhanced-sequence", parameters = {
              @Parameter(name = SequenceStyleGenerator.SEQUENCE_PARAM, value = "BookSequence"),
              @Parameter(name = SequenceStyleGenerator.INITIAL_PARAM, value = "100") })
    protected Long id;

    @Column(nullable = false)
    protected String name;

    protected Address address;
    ...
}


We used the enhanced-sequence generator to generate the identifier values. The Author also has a temporal field containing the date of birth, and the Publisher embeds an Address component. See part 1 for a quick review of these features.

Mapping a Many-to-One Association

An Author should be associated with a Book. An author can have many books written by him or her, while every book must have an author. On the Book side, there is a many-to-one association to Author. Let’s focus on this side first, which is the starting point in such associations. We introduce a field author in the Book class and map it with a @ManyToOne annotation. Similarly, a field publisher is defined to represent the association to a Publisher:

import javax.persistence.ManyToOne;

@Entity
public class Book {
    ...

    @ManyToOne
    @JoinColumn(name = "AUTHOR_ID", nullable = false)
    protected Author author;

    @ManyToOne(optional = false) // equivalent to nullable = false
    @JoinColumn(name = "PUBLISHER_ID")
    protected Publisher publisher;

    ... // getters and setters
}


The resulting schema is shown in the following DDL:

create table Book (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    volumes integer not null,
    AUTHOR_ID bigint not null,
    PUBLISHER_ID bigint not null,
    primary key (id)
)

alter table Book 
   add constraint FKe3rppuv3qa0j4ewpn52qfljn6 
   foreign key (AUTHOR_ID) 
   references Author

alter table Book 
   add constraint FKrb2njmkvio5mhe42empuaiphu 
   foreign key (PUBLISHER_ID) 
   references Publisher


So now,  the Book table has two foreign key columns AUTHOR_ID and PUBLISHER_ID, as specified in the @JoinColumn annotations. They are also non-nullable, making the author and publisher fields required in a Book object before being persisted in a session.

Bidirectional Association With One-to-Many

Each of these many-to-one associations (from Book to Author and from Book to Publisher) is so far a unidirectional association. The other side does not map an association to Book, because it is optional to do so. We can now get the author of any loaded Book instance by accessing its author property. If we want to get all books written by an author, we can write a JPQL query and execute it. However, if this specific query is commonly needed, it may be a good idea to map the other side, for example by having a List<Book> in the Author class annotated with @OneToMany. This way, Hibernate automatically executes select * from Book where AUTHOR_ID = ? whenever we call author.getBooks():

import javax.persistence.OneToMany;

@Entity
public class Author {
    ...

    @OneToMany(mappedBy = "author")
    protected List<Book> books = new ArrayList<>();

    ... // getters and setters

    public void addBook(Book book) {
        this.books.add(book);
        book.setAuthor(this);
    }
}


The mappedBy element in @OneToMany specifies which field is the owner of this bidirectional association. It is the author field in the Book class. This fits naturally as an author “owns” their books. In terms of the DB schema, nothing changes: Hibernate relies on the foreign key column mapped earlier on the author to construct the SQL that selects all books of a given author.

Two things are important to remember. First, whenever we load an Author object from the database using Hibernate, its books are not immediately fetched. Only when we access it (e.g. using a getter) will it send the SQL to fetch and load all its Book instances. This is called lazy fetching and is the default behavior for one-to-many relationships. It can be switched to eager fetching if needed. Note that for many-to-one association (@ManyToOne), the default is eager fetching.

Second, managing a bidirectional entity association requires that both sides be consistent. Whenever we set the author of a book using setAuthor(), we should also add the book to the author’s list. A recommendation is to group this in a helper method, as shown above in addBook().

Unidirectional One-to-Many Association

Although it seems less likely to be used, we can also have the one-to-many association on one side. In this case, the mappedBy element used above would not work because Book no longer has an association to Author. Instead, we use a @JoinColumn to tell Hibernate which column to use in order to fetch Book instances when given an author id:

@Entity
public class Author {

    @OneToMany
    @JoinColumn(name = "AUTHOR_ID")
    protected List<Book> books = new ArrayList<>();

    ...
}


Hibernate would then use that column as a foreign key for retrieving all books belonging to an author:

create table Book (
    id bigint not null,
    publishingDate date,
    title varchar(255) not null,
    volumes integer not null,
    PUBLISHER_ID bigint not null,
    AUTHOR_ID bigint,
    primary key (id)
)

create table Author (
    id bigint not null,
    birthDay date,
    country varchar(30) not null,
    name varchar(255) not null,
    primary key (id)
)

alter table Book 
    add constraint FKe3rppuv3qa0j4ewpn52qfljn6 
    foreign key (AUTHOR_ID) 
    references Author


More on Collection and Association Mappings

This post illustrated simple examples of mapping collections and/or associations between entities. Of course, there many JDK collections supported by Hibernate, and other examples of entity associations which you can find in the Hibernate documentation.

Stay tuned for part 3…

Further Reading

Introduction to JPA Architecture

Java Persistence Done Right

[DZone Refcard] Getting Started With JPA

Topics:
java ,hibernate ,jpa

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}