Pages

Tuesday, August 13, 2013

JPA-маппинг встроенных объектов

Встроенный объект (embedded object) входит в состав сущности и не может существовать отдельно от нее.
Обычно у встроенного объекта нет соответствующей таблицы в базе данных и его поля соответствуют колонкам таблицы сущности, к которой он относится.

Рассмотрим маппинг таких объектов.

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

Создадим базу данных jpa_embedded_objects_mappings_tutorial с таблицами artists и labels.
Колонки artist_city и artist_country таблицы artists хранят данные об адресах исполнителей.
Аналогично, колонки label_city и label_country таблицы labels хранят данные об адресах лейблов.
Добавим данные об исполнителе и лейбле:
CREATE DATABASE jpa_embedded_objects_mappings_tutorial;
USE jpa_embedded_objects_mappings_tutorial;

CREATE TABLE artists (
artist_id INT PRIMARY KEY,
artist_name VARCHAR(50),
artist_city VARCHAR(50),
artist_country VARCHAR(50)
);

CREATE TABLE labels (
label_id INT PRIMARY KEY,
label_name VARCHAR(50),
label_city VARCHAR(50),
label_country VARCHAR(50)
);

INSERT INTO artists VALUES (1, 'Franz Ferdinand', 'Glasgow', 'Scotland');
INSERT INTO labels VALUES (1, 'Domino', 'London', 'England');

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

Создадим класс Address с полями city и country.
Для того, чтобы класс Address стал классом встроенного объекта, добавим аннотацию @Embeddable.
Добавим полю city аннотацию @Column с указанием колонки, которая хранит данные о городе исполнителя, т.е. artist_city.
Добавим полю country аннотацию @Column с указанием колонки, которая хранит данные о стране исполнителя, т.е. artist_country:
package com.jpa.embedded.objects.mappings.tutorial.domain;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class Address {

    @Column(name = "artist_city")
    private String city;

    @Column(name = "artist_country")
    private String country;

    public Address() {

    }

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return "Address{" +
                "city='" + city + '\'' +
                ", country='" + country + '\'' +
                '}';
    }
}

Создадим класс сущности Artist с полями id, name и address.
Поле address ссылается на встроенный объект, поэтому добавим аннотацию @Embedded:
package com.jpa.embedded.objects.mappings.tutorial.domain;

import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

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

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

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

    @Embedded
    private Address address;

    public Artist() {

    }

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

    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 Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

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

Создадим класс ArtistService:
package com.jpa.embedded.objects.mappings.tutorial.service;

import com.jpa.embedded.objects.mappings.tutorial.domain.Address;
import com.jpa.embedded.objects.mappings.tutorial.domain.Artist;

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

public class ArtistService {

    private EntityManager em;

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

    public Artist createArtist(int id, String name, Address address) {
        Artist artist = new Artist(id, name, address);
        em.persist(artist);

        return artist;
    }

    public void removeArtist(int id) {
        Artist artist = em.find(Artist.class, id);

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

    public Artist changeArtistName(int id, String name) {
        Artist artist = em.find(Artist.class, id);

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

        return artist;
    }

    public Artist findArtist(int id) {
        return em.find(Artist.class, id);
    }

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

Добавим класс Artist в 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="JpaEmbeddedObjectsMappingsTutorial" transaction-type="RESOURCE_LOCAL">
        <class>com.jpa.embedded.objects.mappings.tutorial.domain.Artist</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_embedded_objects_mappings_tutorial"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
        </properties>
    </persistence-unit>

</persistence>

В методе main() класса JpaEmbeddedObjectsMappingsTutorial создается объект типа ArtistService, вызывается метод для получения сущности типа Artist и выводится информация об исполнителе:
package com.jpa.embedded.objects.mappings.tutorial;

import com.jpa.embedded.objects.mappings.tutorial.domain.Artist;
import com.jpa.embedded.objects.mappings.tutorial.service.ArtistService;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JpaEmbeddedObjectsMappingsTutorial {

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

        System.out.println("--- Find artist ---");
        Artist artist = artistService.findArtist(1);
        System.out.println(String.format("Found: %s", artist));
    }
}

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

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

Создадим класс сущности Label с полями id, name и address.
Поле address тоже ссылается на встроенный объект, поэтому добавим аннотацию @Embedded.
Поля класса Address ссылаются на колонки artist_city и artist_country, но нам необходимо, чтобы они ссылались на колонки label_city и label_country. Для этого:
  • добавим полю address аннотацию @AttributeOverrides
  • добавим аннотацию @AttributeOverride с указанием поля city класса Address и имени колонки таблицы, которая хранит данные о городе лейбла, т.е. label_city
  • добавим аннотацию @AttributeOverride с указанием поля country класса Address и имени колонки таблицы, которая хранит данные о стране лейбла, т.е. label_country
package com.jpa.embedded.objects.mappings.tutorial.domain;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "labels")
public class Label {

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

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

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "city", column = @Column(name = "label_city")),
            @AttributeOverride(name = "country", column = @Column(name = "label_country"))
    })
    private Address address;

    public Label() {

    }

    public Label(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    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 Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

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

Создадим класс LabelService:
package com.jpa.embedded.objects.mappings.tutorial.service;

import com.jpa.embedded.objects.mappings.tutorial.domain.Address;
import com.jpa.embedded.objects.mappings.tutorial.domain.Label;

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

public class LabelService {

    private EntityManager em;

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

    public Label createLabel(int id, String name, Address address) {
        Label label = new Label(id, name, address);
        em.persist(label);

        return label;
    }

    public void removeLabel(int id) {
        Label label = em.find(Label.class, id);

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

    public Label changeLabelName(int id, String name) {
        Label label = em.find(Label.class, id);

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

        return label;
    }

    public Label findLabel(int id) {
        return em.find(Label.class, id);
    }

    public List<Label> findAllLabels() {
        TypedQuery<Label> query = em.createQuery("SELECT l FROM Label l", Label.class);
        return query.getResultList();
    }
}

Добавим класс Label в 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="JpaEmbeddedObjectsMappingsTutorial" transaction-type="RESOURCE_LOCAL">
        <class>com.jpa.embedded.objects.mappings.tutorial.domain.Artist</class>
        <class>com.jpa.embedded.objects.mappings.tutorial.domain.Label</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_embedded_objects_mappings_tutorial"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
        </properties>
    </persistence-unit>

</persistence>

В методе main() класса JpaEmbeddedObjectsMappingsTutorial создается объект типа LabelService, вызывается метод для получения сущности типа Label и выводится информация о лейбле:
package com.jpa.embedded.objects.mappings.tutorial;

import com.jpa.embedded.objects.mappings.tutorial.domain.Artist;
import com.jpa.embedded.objects.mappings.tutorial.domain.Label;
import com.jpa.embedded.objects.mappings.tutorial.service.ArtistService;
import com.jpa.embedded.objects.mappings.tutorial.service.LabelService;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JpaEmbeddedObjectsMappingsTutorial {

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

        System.out.println("--- Find artist ---");
        Artist artist = artistService.findArtist(1);
        System.out.println(String.format("Found: %s\n", artist));

        System.out.println("--- Find label ---");
        Label label = labelService.findLabel(1);
        System.out.println(String.format("Found: %s", label));
    }
}

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

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

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




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

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

4 comments: