Pages

Tuesday, July 16, 2013

JPA One-to-Many and Many-to-One mappings tutorial

Most of entities have relationships with other entities.
There are four types of relationships:
  • One-to-One
  • One-to-Many
  • Many-to-One
  • Many-to-Many

Let's look at one-to-many and many-to-one relationships mappings. They are similar to mappings described in "JPA One-to-One mappings tutorial" post.

This post is available in Russian.

Let's modify the application created in "JPA table and column mappings tutorial" post.

Music artist might have many albums, but any album relate to only one artist. Let's describe such relationship in our application.

Create jpa_otm_mto_mappings_tutorial database with artists and albums tables.
artists table cannot have a column where albums identifiers are stored, because there could be different number of them. But albums table can store artist identifier for every album. That's why artist_id column of albums table is a foreign key that references artists table.
Add artist and its albums data:
CREATE DATABASE jpa_otm_mto_mappings_tutorial;
USE jpa_otm_mto_mappings_tutorial;

CREATE TABLE artists (
artist_id INT PRIMARY KEY,
artist_name VARCHAR(30)
);

CREATE TABLE albums (
album_id INT PRIMARY KEY,
album_name VARCHAR(50), 
album_year INT, 
artist_id INT, 
FOREIGN KEY (artist_id) REFERENCES artists (artist_id)
);

INSERT INTO artists VALUES (1, 'Franz Ferdinand');
INSERT INTO albums VALUES (1, 'Franz Ferdinand', 2004, 1);
INSERT INTO albums VALUES (2, 'You Could Have It So Much Better', 2005, 1);
INSERT INTO albums VALUES (3, 'Tonight: Franz Ferdinand', 2009, 1);

Unidirectional mapping


If Album entities have references to Artist entity, but Artist entity doesn't have a reference to collection of Album entities, then such relationship is unidirectional.

Create Album class with id, name, year and artist properties.
To create unidirectional many-to-one mapping add @ManyToOne and @JoinColumn annotations to artist property. @JoinColumn annotation name element must reference a foreign key column of album table, e.g. artist_id:
package com.jpa.otm.mto.mappings.tutorial.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "albums")
public class Album {

    @Id
    @Column(name = "album_id")
    private int id;

    @Column(name = "album_name")
    private String name;

    @Column(name = "album_year")
    private int year;

    @ManyToOne
    @JoinColumn(name = "artist_id")
    private Artist artist;

    public Album() {

    }

    public Album(int id, String name, int year) {
        this.id = id;
        this.name = name;
        this.year = year;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public Artist getArtist() {
        return artist;
    }

    public void setArtist(Artist artist) {
        this.artist = artist;
    }

    @Override
    public String toString() {
        return "Album{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", year=" + year +
                '}';
    }
}

Create AlbumService class:
package com.jpa.otm.mto.mappings.tutorial.service;

import com.jpa.otm.mto.mappings.tutorial.domain.Album;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;

public class AlbumService {

    private EntityManager em;

    public AlbumService(EntityManager em) {
        this.em = em;
    }

    public Album createAlbum(int id, String name, int year) {
        Album album = new Album(id, name, year);
        em.persist(album);

        return album;
    }

    public void removeAlbum(int id) {
        Album album = em.find(Album.class, id);

        if (album != null) {
            em.remove(album);
        }
    }

    public Album changeAlbumName(int id, String name) {
        Album album = em.find(Album.class, id);

        if (album != null) {
            album.setName(name);
        }

        return album;
    }

    public Album findAlbum(int id) {
        return em.find(Album.class, id);
    }

    public List<Album> findAllAlbums() {
        TypedQuery<Album> query = em.createQuery("SELECT a FROM Album a", Album.class);
        return query.getResultList();
    }
}

Add Album class to persistence unit in persistence.xml file:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">

    <persistence-unit name="JpaOtmMtoMappingsTutorial" transaction-type="RESOURCE_LOCAL">
        <class>com.jpa.otm.mto.mappings.tutorial.domain.Artist</class>
        <class>com.jpa.otm.mto.mappings.tutorial.domain.Album</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:mysql://localhost:3306/jpa_otm_mto_mappings_tutorial"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
        </properties>
    </persistence-unit>

</persistence>

Add code that creates AlbumService object, finds Album entities and prints albums and related artists data to main() method of JpaOtmMtoMappingsTutorial class:
package com.jpa.otm.mto.mappings.tutorial;

import com.jpa.otm.mto.mappings.tutorial.domain.Album;
import com.jpa.otm.mto.mappings.tutorial.service.AlbumService;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.List;

public class JpaOtmMtoMappingsTutorial {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaOtmMtoMappingsTutorial");
        EntityManager em = emf.createEntityManager();        
        AlbumService albumService = new AlbumService(em);

        System.out.println("--- Finding albums ---");
        List<Album> albums = albumService.findAllAlbums();
        for (Album album : albums) {
            System.out.println(String.format("Found album: %s", album));
            System.out.println(String.format("Album artist: %s", album.getArtist()));
            System.out.println();
        }        
    }
}

Execute main() method:

Application has printed albums and related artists data.

Bidirectional mapping


If Album entities have a reference to Artist entity, and Artist entity has a reference to a collection of Album entities, then such relationship is bidirectional.

This can be done in two ways:
  • create albums property of List<Album> type in Artist class
  • add @OneToMany annotation to albums property
  • add mappedBy annotation element, which references to Album entity property with @ManyToOne and @JoinColumn annotations, e.g. artist
  • @OneToMany(mappedBy = "artist")
    private List<Album> albums;
or
  • create albums property of List type in Artist class
  • add @OneToMany annotation to albums property
  • add targetEntity annotation element, which references to class of list elements, e.g. Album.class
  • add mappedBy annotation element
  • @OneToMany(targetEntity = Album.class, mappedBy = "artist")
    private List albums;

Let's do it in the first way:
package com.jpa.otm.mto.mappings.tutorial.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import java.util.List;

@Entity
@Table(name = "artists")
public class Artist {

    @Id
    @Column(name = "artist_id")
    private int id;

    @Column(name = "artist_name")
    private String name;

    @OneToMany(mappedBy = "artist")
    private List<Album> albums;   

    public Artist() {

    }

    public Artist(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Album> getAlbums() {
        return albums;
    }

    public void setAlbums(List<Album> albums) {
        this.albums = albums;
    }

    @Override
    public String toString() {
        return "Artist{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Add code that creates ArtistService object, finds Artist entity and prints artist and related albums data to main() method of JpaOtmMtoMappingsTutorial class:
package com.jpa.otm.mto.mappings.tutorial;

import com.jpa.otm.mto.mappings.tutorial.domain.Album;
import com.jpa.otm.mto.mappings.tutorial.domain.Artist;
import com.jpa.otm.mto.mappings.tutorial.service.AlbumService;
import com.jpa.otm.mto.mappings.tutorial.service.ArtistService;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.List;

public class JpaOtmMtoMappingsTutorial {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaOtmMtoMappingsTutorial");
        EntityManager em = emf.createEntityManager();
        AlbumService albumService = new AlbumService(em);
        ArtistService artistService = new ArtistService(em);

        System.out.println("--- Finding albums ---");
        List<Album> albums = albumService.findAllAlbums();
        for (Album album : albums) {
            System.out.println(String.format("Found album: %s", album));
            System.out.println(String.format("Album artist: %s", album.getArtist()));
            System.out.println();
        }

        System.out.println("--- Finding artist ---");
        Artist artist = artistService.findArtist(1);
        System.out.println(String.format("Found artist: %s", artist));
        for (Album album : artist.getAlbums()) {
            System.out.println(String.format("Artist album: %s", album));
        }
    }
}

Execute main() method:

Application has printed artist and related albums data, which means we have successfully created unidirectional and bidirectional one-to-many and many-to-one mappings during application development.

Folder structure:




Application source code is available at https://code.google.com/p/jpa-otm-mto-mappings-tutorial/.

Recommended posts:

4 comments: