Реализация составных ключей с помощью JPA и Hibernate

Проблема устаревшей схемы базы данных

Сегодня, с широким внедрением и использованием инструментов объектно-реляционного отображения (Object-Relational Mapping, ORM), не приходится задумываться о таких сложных вещах, как составные ключи. Обычно для построения ключей достаточно целых чисел, а само построение вполне можно доверить инструментальным средствам. Но иногда встречаются ситуации, когда требуется составной ключ, и тогда необходима определенная стратегия. В этой статье приводятся советы по реализации составных ключей с помощью JPA и Hibernate.

Стивен Моррис, технический директор, Omey Communications

Стивен Моррис (Stephen B. Morris) работает техническим директором ирландской компании Omey Communications. За 20 лет работы ему довелось работать во многих сетевых компаниях мира над самыми разнообразными проектами, в их числе система сетевого управления предприятием (на основе Java 2) J2EE/J2SE, программы выставления счетов, простой протокол управления сетью, подключения к сети SNMP (Простой Протокол Управления Сетью), оборудование для сетей и приложения для сетей мобильной связи стандарта GSM. Он опубликовал книгу "Управление сетью, базы управляющей информации MIB и многопротокольная коммутация MPLS: принципы, дизайн и осуществление" (Network Management, MIBs and MPLS: Principles, Design and Implementation (издательство Prentice Hall PTR, 2003 г.), а также ряд статей, в основном по управлению сетью для сайтов InformIT и OnJava.com. Электронный адрес Стивена: stephenbjm@yahoo.com.



24.09.2010

Постановка задачи

Начнем с простого описания задачи: определить составной ключ базы данных. Это ключ, объединяющий несколько столбцов для уникального определения строк таблицы базы данных. Иногда составные ключи называют естественными ключами (natural keys) или бизнес-ключами. Иногда составные ключи используются потому, что выбор ключа некоторым образом связан с областью деятельности конечного пользователя. Для определения составного ключа просто берутся и объединяются некоторые атрибуты из этой области, что и обеспечивает требуемую степень уникальности строк. Недостаток составных ключей в том, что их трудно проектировать и программировать. Кроме того, они склонны привязывать ваш проект базы данных и ORM к исходной области, что может стать источником проблем.


Код сущности

В листинге 1 приведен класс Java, названный BillingAddress. Этот класс моделирует платежный адрес лица или организации. Само составление счета связано с другим классом Java, названным PurchaseOrder. Здесь нет ничего особенного — сначала размещается заказ на покупку, затем следует составление счета.

Листинг 1. Класс BillingAddress
import javax.persistence.*;
import java.io.Serializable;

@Embeddable
public class BillingAddress implements Serializable {

    private String street;
    private String city;

    BillingAddress() {}

    public BillingAddress(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    private void setStreet(String street) {
        this.street = street;
    }

    public String getCity() {
        return city;
    }

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

Обратите внимание на один важный момент: этот класс реализует Java-интерфейс Serializable. Обратим также внимание на строку с аннотацией @Embeddable. Эта аннотация является первой деталью головоломки составного ключа. Java-класс с аннотацией @Embeddable сам может быть подкомпонентом другого класса. Это только звучит сложно, на самом деле все просто. В качестве примера в листинге 2 приводится класс PurchaseOrder, использующий класс BillingAddress из листинга 1.

Листинг 2. Класс PurchaseOrder
import javax.persistence.*;

@Entity
@Table(name = "PURCHASE_ORDERS")
@IdClass(BillingAddress.class)
public class PurchaseOrder {

	PurchaseOrder() {}

	PurchaseOrder(BillingAddress billingAddress) {
	street = billingAddress.getStreet();
	city = billingAddress.getCity();
	}

	@Id
	@AttributeOverrides({
	@AttributeOverride(name = "street",
	column = @Column(name="STREET")),
	@AttributeOverride(name = "city",
	column = @Column(name="CITY"))
	})

	private String street;
	private String city;
	private String itemName;

	public String getItemName() {
	return itemName;
	}

	public void setItemName(String itemName) {
	this.itemName = itemName;
	}
}

Мне всегда казалось, что аннотации трудны для чтения, и листинг 2 не является исключением, поэтому я разбил его на удобные для просмотра фрагменты. Первая аннотация —@Entity, она показывает, что этот класс является объектом базы данных (то есть, он будет частью ORM-решения). Обычно с @Entity ожидается соответствующая таблица базы данных, и на неё указывает следующая аннотация — а именно @Table. Мне кажется, что когда процесс разбивают таким образом, его проще понять.

Следующей в листинге 2 идет аннотация @IdClass, которая определяет ссылку на класс составного ключа. Возможно, вы заметили, что эта аннотация ссылается на класс BillingAddress из листинга 1. Пропустив конструкторы в листинге 2, обратите внимание на аннотацию @Id. Именно здесь вложенными аннотациями @AttributeOverrides определяется составной ключ. Эти аннотации используются для определения столбцов составного ключа: "STREET" и "CITY" соответственно.

Сразу поcле аннотаций в листинге 2 повторяются две строки из листинга 1. Повторяющийся код — это, конечно, два скрытых члена класса: street и city. Это дублирование нужно для создания составного ключа.


Схема базы данных

Мы рассмотрели техническую сторону вопроса. Теперь посмотрим, как это выразится на практике, создав схему базы данных. В листинге 3 приведена схема базы данных простейшего ORM-проекта.

Листинг 3. Схема базы данных
drop table PURCHASE_ORDERS if exists;

create table PURCHASE_ORDERS (
        street varchar(255) not null,
        city varchar(255) not null,
        itemName varchar(255),
        primary key (street, city)
);

Видно, что первичный ключ действительно является составным, собранным из полей street и city. Как это выглядит в реальной базе данных — например, в базе данных с графическим инструментарием? Прежде чем ответить, я напишу простой код клиента для сохранения одной или двух сущностей в базе данных.

В листинге 4 приведен фрагмент программы, создающий объекты ранее определенных классов.

Листинг 4. Код ORM-клиента
        // Start EntityManagerFactory
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("helloworld");

        // First unit of work
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        PurchaseOrder purchaseOrder = 
          new PurchaseOrder(new BillingAddress("Broad Street", "Boston"));
        purchaseOrder.setItemName("My new computer");
        em.persist(purchaseOrder);

        tx.commit();
        em.close();

Программа из листинга 4 иллюстрирует путь от создания и настройки объекта PurchaseOrder до сохранения этого объекта в базе данных. Наглядная иллюстрация волшебной силы ORM. Посмотрим, что здесь происходит.

Для начала создается экземпляр EntityManagerFactory, который, в свою очередь, используется для создания экземпляра EntityManager с именем em. Последний затем используется для записи экземпляра объекта PurchaseOrder в базу данных. Фактическое сохранение в базу данных происходит как часть транзакции.

Транзакция — это набор элементарных действий, которые либо выполняются успешно, либо возвращаются назад в случае ошибки. Как можно видеть из листинга 4, объект EntityManager используется для создания экземпляра EntityTransaction с именем tx. Именно последний объект оформляет элементарную операцию в транзакцию.

Обратите внимание на вызовы persist() и commit(). Важно помнить, что в базе данных не произойдёт никаких изменений, пока не будут выполнены оба этих вызова. Это простая модель Java Persistence API (JPA).

Чтобы завершить обзор ORM, посмотрим, как выглядит база данных после выполнения программы из листинга 4 (рисунок 1). Программа была протестирована с использованием HSQLDB, базы данных в памяти, куда также входит простая графическая утилита. Состояние базы данных HSQLDB показано на рисунке 1. Можно видеть, что был запущен SQL-запрос по таблице PURCHASE_ORDERS. Эта таблица была создана из схемы, которая, в свою очередь, была создана из программы на Java в предыдущих листингах.

Рисунок 1. Заполненная база данных
Заполненная база данных

На рисунке 1 можно видеть результат выполнения строки из листинга 4: purchaseOrder.setItemName("My new computer"). Вызов setter-метода заполнил соответствующий столбец в строке базы данных строковыми данными: "My new computer". С точки зрения рабочего процесса можно представить себе работу всей программы как создание заказа на покупку нового компьютера, за которым следует процесс составления счета. Все шаги рабочего процесса неявно сохраняются в базе данных.


Завершающие комментарии о составных ключах

Составной ключ, определенный в листингах 1 и 2, дает возможность связать вместе ряд столбцов. Такое сочетание столбцов обеспечивает требуемую уникальность, давая возможность иметь произвольное число строк в таблице базы данных, что и было продемонстрировано. Теперь вкратце опишем, почему такой необычный подход может потребоваться для проектирования базы данных.

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

Мой опыт показывает, что в подобных случаях возражать против такого подхода бессмысленно. Если составные ключи являются стандартом, вряд ли эта ситуация изменится в ближайшее время. Во многих случаях уже существует огромное количество данных, структурированных таким образом. Поэтому переход с составных ключей на числовые потребовал бы миграции старых данных. Кроме того, существуют бизнес-процессы, которые отображаются на данные составных ключей. Сочетание всех этих факторов может сделать использование составных ключей неизбежным.

Ресурсы

Научиться

Получить продукты и технологии

  • Вики по JPA узнайте подробнее о применении Java Persistence API для хранения данных и ORM для платформы Java EE.(EN)
  • Hibernate материалы для чтения, загрузка программ и многое другое.(EN)
  • Используйте в вашем следующем проекте ознакомительное программное обеспечение IBM, которое можно загрузить через Интернет или заказать на DVD.(EN)
  • Загрузите ознакомительные версии продуктов IBM или поэкспериментируйте в «песочнице» IBM SOA Sandbox и приобретите опыт использования инструментов разработки приложений и связующего ПО семейств DB2®, Lotus®, Rational®, Tivoli® и WebSphere®.(EN)

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Open source, Технология Java
ArticleID=547759
ArticleTitle=Реализация составных ключей с помощью JPA и Hibernate
publish-date=09242010