Sunday, June 23, 2013

JPA-маппинг отношений "один-ко-многим" и "многие-к-одному"

Многие сущности имеют отношения с другими сущностями.
Отношения бывают 4 типов:
  • One-to-One (один-к-одному)
  • One-to-Many (один-ко-многим)
  • Many-to-One (многие-к-одному)
  • Many-to-Many (многие-ко-многим)

Рассмотрим маппинг отношений типов "один-ко-многим" и "многие-к-одному". Он во многом будет напоминать маппинг, описанный в посте "JPA-маппинг типа "один-к-одному"".

Возьмем за основу приложение, созданное в посте "JPA-маппинг сущности и ее свойств".

Музыкальный исполнитель может иметь много альбомов, но альбом может относиться только к одному исполнителю. Опишем эти отношения в приложении.

Создадим базу данных jpa_otm_mto_mappings_tutorial и таблицы artists и albums.
Таблица artists не может иметь колонку, которая хранит все идентификаторы альбомов исполнителя, так как их может быть разное количество. Но таблица albums может хранить идентификатор исполнителя для каждого альбома. Поэтому колонка artist_id таблицы albums является внешним ключом и ссылается на одноименную колонку таблицы artists.
Добавим данные об исполнителе и его альбомах:
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);

Однонаправленный маппинг


Если сущности типа Album будут иметь ссылку на сущность Artist, но сущность Artist не будет ссылаться на сущности типа Album, то отношения будут однонаправленными.

Создадим класс сущности Album с полями id, name, year и artist.
Для однонаправленного маппинга типа "многие-к-одному" добавим полю artist аннотации @ManyToOne и @JoinColumn с указанием имени колонки таблицы albums, которая является внешним ключом, т.е. 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 +
                '}';
    }
}

Создадим класс AlbumService:
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();
    }
}

Добавим класс Album в persistence unit в файле persistence.xml:
<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>

В методе main() класса JpaOtmMtoMappingsTutorial создается объект типа AlbumService, вызывается метод для получения сущностей типа Album и выводится информация об альбомах и их исполнителях:
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();
        }        
    }
}

Запустим метод main():

Приложение вывело информацию об альбомах и их исполнителях.

Двунаправленный маппинг


Если сущности типа Album и сущность Artist будут иметь ссылки друг на друга, то отношения будут двунаправленными.

Это можно сделать двумя способами:
  • создадим поле albums типа List<Album> в классе Artist
  • добавим полю albums аннотацию @OneToMany
  • добавим элемент аннотации mappedBy, который будет указывать на имя поля класса Album, которое имеет аннотации @ManyToOne и @JoinColumn, т.е. artist
    @OneToMany(mappedBy = "artist")
    private List<Album> albums;
или
  • создадим поле albums типа List в классе Artist
  • добавим полю albums аннотацию @OneToMany
  • добавим элемент аннотации targetEntity, который будет указывать класс элементов списка
  • добавим элемент аннотации mappedBy
    @OneToMany(targetEntity = Album.class, mappedBy = "artist")
    private List albums;

Выберем первый способ:
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 + '\'' +
                '}';
    }
}

Добавим в метод main() класса JpaOtmMtoMappingsTutorial создание объекта типа ArtistService, вызов метода для получения сущности Artist и вывод информации об исполнителе и его альбомах:
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));
        }
    }
}

Запустим метод main():

Приложение вывело информацию об исполнителе и его альбомах, а значит мы успешно сделали однонаправленный и двунаправленный маппинги типов "один-ко-многим" и "многие-к-одному".

Структура проекта:




Исходный код созданного приложения доступен по ссылке https://code.google.com/p/jpa-otm-mto-mappings-tutorial/.

Рекомендуемые посты:

3 comments:

  1. Всё статьи написаны понятно, просто и дают отличное представление о том в каком случае что применять.
    Спасибо!

    ReplyDelete
  2. Доброго времени, не могли бы вы пояснить в каких случаях можно не закрывать EntityManagerFactory и EntityManager, изучаю хибер и в коде по факту ничем не отличающемся от приведенного в этой статье, происходит зависание в самом конце,но если не закрыть эти самые манагеры то приложение закрывается нормально...

    ReplyDelete