Рейтинг@Mail.ru

Apache Cayenne: знакомимся и создаём простое приложение

Автор: Alex. Опубликовано в Программирование . просмотров: 11415

Рейтинг:  4 / 5

Звезда активнаЗвезда активнаЗвезда активнаЗвезда активнаЗвезда не активна
 

Вам нужна объектная база данных? Ваше приложение должно уметь работать с разными СУБД? Вы не очень хорошо знаете SQL? Если вы ответили «да», хотя бы на один вопрос, то вам стоит узнать, что из себя представляет платформа Apache Cayenne и как её использовать.

Apache Cayenne

Всего про Apache Cayenne будет две связанных статьи. В этой первой статье вы найдёте общую информацию об Apache Cayenne, а также научитесь создавать простое работающее приложение использующее Apache Cayenne. Во второй статье мы конвертируем приложение, созданное в первой статье, в веб-приложение и научим работать его с СУБД ЛИНТЕР.

Об Apache Cayenne

Прежде всего, давайте посмотрим, что такое Apache Cayenne. Если вы не знали, то Apache Cayenne - это платформа со 100%-но открытыми исходниками и доступная бесплатно по лицензии Apache, которая обеспечивает объектно-реляционное отображение данных (object-relational mapping, ORM) и позволяет создавать удалённые сервисы для доступа к ним. Первое достигается благодаря тому, что Cayenne легко связывает одну или несколько баз данных напрямую с Java-объектами, которые автоматически управляют транзакциями и последовательностями, генерируют SQL-запросы, склеивают табличные данные и т.д. Второе достигается благодаря постоянству удалённых объектов Cayenne (Cayenne's Remote Object Persistence), что позволяет выгружать такие объекты клиентам через веб-сервисы.

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

Также Cayenne поддерживает множество других функций, включая кеширование, полноценный синтаксис объектных запросов, предварительную загрузку взаимосвязей, наследование объектов, авто-обнаружение базы данных, универсальные постоянные объекты (generic persisted objects).

Официальная страничка проекта находится здесь.

Почему Cayenne?

Почему разработчики выбирают Cayenne? Потому, что Cayenne позволяет работать только с Java-объектами, абстрагируясь от базы данных. Вот преимущества, которые вы получаете при использовании Cayenne:

      • Переносимость практически между любыми базами данных, для которых есть JDBC-драйвер, без изменения кода вашего приложения.
      • Не требуется знание SQL, хотя оно всегда полезно.
      • Код проверки фиксируемых данных можно вставлять внутрь операций объектов. Например, вы можете делать простую проверку количества символов в пароле или комплексную проверку на правомерность набора операций в транзакции связанной с изменением в главной бухгалтерской книге. Это позволяет вам убрать проверочный код из GUI слоя и обеспечить улучшенную защиту от программных ошибок.
      • Кеширование чтобы сделать ваше приложение быстрее и избежать повторных обращений к базе данных для тех же данных.
      • Поддержка ленивой загрузки взаимосвязей легко меняется на предварительную загрузку для повышения производительности, когда это потребуется.
      • Разбивка на страницы, позволяющая понизить количество загружаемых данных и сократить время выполнения запросов. Вот классический пример: вам нужно загрузить 97 записей, а конечному пользователю показать только 10. С помощью разбивки на страницы, будут полностью загружены только 10 записей. Cayenne будет автоматически подгружать остальные страницы с записями, если они потребуются.
      • Настраиваемые блокировки, обеспечивающие интеграцию данных и предотвращающие появление спорных данных, например, когда одна утилита меняет данные, в то время когда в Cayenne загружены те же данные для их последующего изменения.
      • Графический интерфейс моделера базы данных упрощающий обучение Cayenne. Моделер работает с XML-файлами, которые могут быть легко отредактированы вручную, в случае необходимости.
      • Cayenne может работать по трёхзвенной схеме, когда несколько клиентов подключаются к базе данных не через JDBC, а через специальный сервис Cayenne. Это даёт больший контроль над централизованной проверкой, кешированием и бесшовным постоянством объектов от сервера до клиентов. Клиенты сами могут быть веб-сервисами обеспечивающими балансировку нагрузки веб-ферм или тяжелыми клиентами с графическим интерфейсом, т.е. Swing/SWT-приложениями.
      • Постоянные объекты могут быть не определены во время компиляции. Вместо этого Cayenne может использовать универсальные классы с отображением определённым динамически во время выполнения (без каких либо манипуляций с байт-кодом).
      • Cayenne поддерживает «вложенные контексты», позволяющие делать произвольное количество уровней вложенности для фиксации/отката операции. Таким образом, пользователь может создавать «черновые контексты» для работы с объектами, с возможностью отменить (или сохранить) эти изменения, не затрагивая общий больший набор незафиксированных изменений.

Сопровождение

Если вы будете использовать Cayenne, то вас непременно заинтересует сопровождение. Здесь есть возможность бесплатного общения с сообществом Cayenne или платных сопровождения и консультаций. Про все эти возможности можно узнать здесь.

Итак, с чего начать?

Сразу оговорюсь, что всё написанное в статье относится к последней стабильной версии на момент написания статьи – 3.1. Итак, сначала проверьте JDK, который вы собираетесь использовать. Для Cayenne 3.1 – это должен быть JDK 1.5 или более новый, а для нового Cayenne 4.0JDK 1.6 или более новый. Скачать моделер CayenneModeler и все необходимые файлы можно здесь.

Теперь, что касается IDE, то здесь для экспериментов с Cayenne я буду использовать Eclipse IDE for Java EE Developers Luna SR2 (4.4.2). Все версии Eclipse можно скачать здесь. Русификация Eclipse есть здесь, но переведена лишь малая часть, поэтому, в основном, вы будете видеть всё на английском языке. В статье я не буду использовать русификатор.

Все примеры будут сделаны на основе учебника с официального сайта, но с моими дополнениями и отступлениями.

Для примера в Eclipse должна быть установлена интеграция с Apache Maven «m2eclipse». В версии, которую я использую, она уже есть. А если вам потребуется установка, то выберите меню «Help -> Install New Software», затем щёлкните по «Add...», чтобы добавить новый сайт, и введите «Maven» в поле «Name» и «http://m2eclipse.sonatype.org/sites/m2e» в поле «Location».

Создание проекта

Теперь создадим проект для экспериментов с Cayenne. В IDE Eclipse выберите пункт меню «File -> New -> Other...» и затем в диалоге «Maven -> Maven Project». Нажмите «Next». На следующем шаге выберите опцию «Create a simple project» и щёлкните по «Next». На следующем шаге (см. картинку снизу) заполните поля «Group Id» (в качестве идентификатора группы обычно используется перевёрнутое доменное имя, например, ru.proghouse.cayenne) и «Artifact Id» (просто имя, например, «example») и нажмите кнопку «Finish».

Создание нового проекта Maven в Eclipse

После создания проекта удостоверьтесь, что в настройках Java-компилятора выставлена правильная версия. Для этого щёлкните правой кнопкой мышки по названию проекта «example» и выберите пункт меню «Properties» и, в открытом окне, «Java Compiler». Здесь значение «Compiler compliance level» (уровень соответствия компилятора) для Cayenne 3.1 должен быть 1.5 или выше.

Теперь скачайте нужную версию Cayenne, если вы это ещё не сделали, распакуйте скачанный архив и запустите моделер Cayenne (он находится в папке bin). После запуска вы увидите приветствие.

Первый запуск моделера Apache Cayenne

Нажмите на кнопку «New Project» или пункт меню «File -> New Project». Сразу после создания вы увидите домен данных «project» на панели слева, а на закладках справа его свойства.

Вообще, домен данных (DataDomain) в Cayenne выполняет роль маршрутизатора запросов. Он создаёт единую абстракцию источника данных, скрывая множество физических источников данных от пользователя. Когда дочерний контекст данных (DataContext) посылает запрос домену, тот прозрачно перенаправляет запрос в соответствующий узел данных (DataNode).

Теперь добавьте узел данных. Один узел данных описывает одну базу данных, к которой будет обращаться ваше приложение. Здесь можно добавить много узлов, но для примера мы добавим только один узел. Для этого щёлкните по домену данных правой кнопкой мышки и выберите пункт контекстного меню «Create DataNode» или нажмите на соответствующую кнопку на панели инструментов. После добавления узла справа вы увидите его свойства. Здесь нам нужно настроить JDBC-драйвер.

Для экспериментов создадим базу данных Derby в оперативной памяти (in-memory). Для этого в поле «JDBC Driver» пропишите «org.apache.derby.jdbc.EmbeddedDriver», а в поле «DB URL» - «jdbc:derby:memory:testdb;create=true».

Не забывайте, что в случае, когда база данных находится в оперативной памяти, то все данные пропадут при остановке приложения. Здесь такой подход используется только для примера.

В поле «Schema Update Strategy» выставьте «org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy», чтобы наша in-memory база данных создавалась каждый раз при запуске приложения.

Настройка узла данных в Apache CayenneModeler

Теперь создадим карту данных (DataMap). Карта данных содержит информацию об отображении данных на Java-объекты. Для создания карты данных щёлкните по домену данных правой кнопкой мышки и выберите пункт контекстного меню «Create DataMap» или щёлкните по кнопке «Create DataMap» на панели инструментов. Сразу после создания карта данных автоматически привязывается к узлу, который мы создали на предыдущем шаге. Изменить привязку можно позже в свойствах карты данных с помощью выпадающего списка «DataNode», см. картинку снизу. Все остальные свойства можно оставить без изменений кроме «Java Package». Вбейте здесь имя будущей библиотеки, например, ru.proghouse.cayenne.persistent, это имя будет использоваться для всех постоянных классов.

Настройка карты данных в Apache CayenneModeler

Прежде чем пойти дальше сохраним проект. Щёлкните по кнопке «Save» в панели инструментов, найдите папку с проектом, который вы создали в Eclipse, в ней найдите папку src/main/resources и сохраните ваш проект здесь. Если не можете найти папку, можете найти её в Eclipse на панели «Package Explorer» (в перспективе «Java»), затем щёлкнуть по ней правой кнопкой, выбрать пункт меню «Show In -> Properties» и на открывшейся панели «Properties» посмотреть свойство «location». Теперь в Eclipse щёлкните правой клавишей мышки по названию проекта и в контекстном меню выберите «Refresh». После этого вы увидите файлы Cayenne в проекте, см. картинку.

Отображение файлов Apache Cayenne в IDE Eclipse

Обратите внимание на то, что путь для хранения XML-файлов Cayenne выбран не случайно. В процессе выполнения Cayenne будет автоматически искать файлы «cayenne-*.xml» в папке приложения CLASSPATH и «src/main/resources» должна быть папкой для классов (также это стандартный путь, куда Maven копирует jar-файлы, если вы используете Maven из командной строки).

Объектно-реляционное отображение данных в Cayenne

Итак, давайте теперь для примера сделаем простую объектно-реляционную модель с помощью моделера CayenneModeler. Мы создадим законченную ORM-модель для схемы базы данных, которую вы видите на рисунке снизу.

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

Как правило, у вас уже есть готовая база данных, и вы можете быстро импортировать её с помощью пункта меню «Tools -> Reengineer Database Schema». Так вы сэкономите массу времени по сравнению с разработкой отображения вручную. Однако понимание того, как создать отображение вручную, очень важно, поэтому ниже будет рассмотрен «ручной» подход.

Отображение таблиц и колонок в Cayenne

Сначала сделаем таблицу ARTIST в моделере Cayenne. Для этого выберите «datamap» в дереве проекта на панели слева и щёлкните по кнопке «Create DbEntity» (создать сущность БД) или по пункту меню «Project -> Create DbEntity». Затем в поле «DbEntity Name» (имя сущности БД) укажите имя «ARTIST». Теперь щёлкните на кнопку «Create Attribute» (третья кнопка слева). Это действие поменяет текущую закладку на «Attributes» и добавит новый атрибут (в данной ситуации атрибут означает поле таблицы) с именем «untitledAttr». Задайте новому атрибуту имя «ID», тип «INTEGER» и сделайте его «PK» (первичный ключ), см. картинку. Обратите внимание, что для первичного ключа свойство «Mandatory» (обязательный) выставляется автоматически.

Добавление поля к таблице в Apache CayenneModeler

Аналогично добавьте атрибуты NAME VARCHAR(200) и DATE_OF_BIRTH DATE. Чтобы задать размер поля NAME задайте его в колонке «Max Length».

Добавление текстового поля и поля "дата" в Apache CayenneModeler

Затем проделайте все те же действия для PAINTING и GALLERY для соответствия со схемой, показанной выше.

Добавление нескольких таблиц в Apache CayenneModeler

Не забывайте периодически сохранять проект Cayenne, чтобы не потерять проделанную работу. Также после сохранения не забывайте обновлять проект в Eclipse, т.к. Eclipse не знает о каких-либо изменениях в моделере Cayenne.

Отображение связей базы данных в Cayenne

Теперь нам нужно указать связи между таблицами ARTIST, PAINTING и GALLERY. Итак, приступим к созданию связи одна-ко-многим для таблиц ARTIST и PAINTING:

      • Выберите сущность ARTIST в левой панели и откройте закладку «Relationships» справа.
      • Щёлкните по кнопке «Create Relationship» на панели инструментов (вторая кнопка слева). При этом создастся связь «untitledRel».
      • Выберите в колонке «Target» (цель) таблицу PAINTING.

Добавление связи в Apache CayenneModeler

      • Щёлкните по кнопке «Database Mapping» (Кнопка настройки связи в Apache CayenneModeler) чтобы открыть диалог настройки связи «DbRelationship Info». Здесь вы можете задать имя связи, а также сразу задать имя обратной связи. В качестве имени можно ввести что угодно (это лишь символическое название), однако рекомендуется использовать корректный Java-идентификатор, т.к. это упростит в дальнейшем написание кода. Мы назовём нашу связь «paintings», а обратную связь «artist».
      • Щёлкните по кнопке Add, для добавления точки соединения.
      • Выберите атрибут «ID» в колонке «Source» и «ARTIST_ID» в колонке «Target».

Настройка прямой и обратной связи в Apache CayenneModeler

      • Щёлкните по кнопке «Done», чтобы зафиксировать изменения и закрыть диалог.
      • В результате были созданы две связи: одна связь от ARTIST к PAINTING и одна обратная связь от PAINTING к ARTIST. Однако после создания связей нам нужно обязательно проверить, что для них установлено в колонке «To Many» («ко многим»). Для связи «paintings» галка в колонке «To Many» должна быть установлена, чтобы связь была «ко многим», поставьте её. Затем щёлкните по сущности «PAINTING» на панели слева и уберите галочку «To Many» у связи «artist», чтобы эта связь была «к одному».

Теперь повторите эти шаги, чтобы сделать связи между таблицами PAINTING и GALLERY, назвав их «gallery» и «paintings».

Вот результат, который у вас должен получиться:

Связь "ко многим" в Apache CayenneModeler

Связь "ко многим" в Apache CayenneModeler

Связи "к одному" в Apache CayenneModeler

Отображение Java-классов в Cayenne

Теперь, когда схема базы данных готова, CayenneModeler может создать отображения Java-классов, т.н. объектные сущности («ObjEntities»), на основе сущностей базы данных («DbEntities»). В настоящее время нет возможности сделать это для всего DataMap в один клик, поэтому сделаем это индивидуально для каждой таблицы.

      • Выберите сущность «ARTIST» и щёлкните по кнопке «Create ObjEntity» (Кнопка создания класса в Apache CayenneModeler) на панели инструментов. После этого создастся объектная сущность «Artist», а в поле «Java Class» будет прописано «ru.proghouse.cayenne.persistent.Artist». А все имена сущностей базы данных моделер трансформирует в привычные для Java имена, например, если вы откроете закладку «Attributes», вы увидите, что колонка таблицы «DATE_OF_BIRTH» была преобразована в атрибут «dateOfBirth».
      • Выберите сущность «GALLERY» и опять щёлкните по кнопке «Create ObjEntity». Вы увидите, что создалась объектная сущность «Gallery».
      • Теперь сделайте то же самое для «PAINTING».

Создание классов для таблиц в Apache CayenneModeler

Теперь нужно синхронизировать связи. Сущности Artist и Gallery были созданы в тот момент, когда связанная с ними сущность Painting ещё не была создана, и поэтому связи не были установлены.

      • Щёлкните по сущности «Artist» и откройте закладку «Relationships». Теперь щёлкните по кнопке «Sync ObjEntity with DbEntity» (Кнопка синхронизации в Apache CayenneModeler) на панели инструментов. После этого вы увидите, что связь «paintings» появилась.
      • Сделайте тоже самое для сущности «Gallery».

Синхронизация объектных и табличных связей в Apache CayenneModeler

Ну вот, отображение и закончено. После этого вы можете настроить имена Java-классов, которые были созданы автоматически, если они вас не устраивают.

Создание схемы базы данных в Cayenne

На основе созданной модели CayenneModeler может создать схему базы данных, т.е. скрипт на создание. Для этого есть пункт меню «Tools -> Generate Database Scheme». И во второй статье будет рассмотрено создание скрипта для СУБД ЛИНТЕР. Но пока мы будем использовать in-memory базу данных, и она будет создаваться автоматически при каждом запуске приложения.

Создание Java-классов в Cayenne

Теперь сгенерируем Java-классы на основе модели, которую мы сделали на предыдущих шагах. Делается это с помощью генератора классов в следующей последовательности:

      • Выберите пункт меню «Tools -> Generate Classes».
      • В поле «Type» должно быть выбрано «Standard Persistent Objects».
      • В поле «Output Directory» выберите папку «src/main/java» в папке вашего проекта.

Генерация классов в Apache CayenneModeler

      • Щёлкните по закладке «Classes» (классы) и выберите нужные классы.
      • Щёлкните по кнопке «Generate».

Генерация классов в Apache CayenneModeler

После этого идите в Eclipse, щёлкните по проекту правой кнопкой мышки, выберите пункт контекстного меню «Refresh», и вы увидите пары классов, созданные для каждой сущности.

Отображение сгенерированных классов в Eclipse

Возможно, также вы увидите красные крестики рядом с новыми классами (как на картинке сверху), из-за того, что Cayenne не включен в наш проект, как Maven-зависимость. Исправить это можно добавив артефакт «cayenne-server» в конец файла pom.xml. В результате POM должен выглядеть примерно так:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>ru.proghouse.cayenne</groupId>
   <artifactId>example</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <dependencies>
      <dependency>
         <groupId>org.apache.cayenne</groupId>
         <artifactId>cayenne-server</artifactId>
         <!-- Здесь нужно указать версию Cayenne, которую вы используете -->
         <version>3.1</version>
      </dependency>
   </dependencies>
</project>

Перед сохранением изменений убедитесь, что ваш компьютер подключен к Интернету. Когда вы будете сохранять файл pom.xml, Eclipse загрузит необходимые jar-файлы Cayenne и добавит пути к ним в свойствах проекта. После сохранения изменений красные крестики исчезнут.

Корректировка файла pom.xml в Eclipse

Теперь давайте посмотрим на пары созданных классов. Каждая пара состоит из суперкласса (такого как _Artist) и подкласса (такого как Artist). Вы ни в коем случае не должны изменять суперклассы, которые начинаются со знака подчёркивания, т.к. они будут пересоздаваться при каждом запуске генератора. Всю вашу логику вы должны реализовывать в подклассах в пакете «ru.proghouse.cayenne.persistent» (в классах без подчёркивания), файлы которого никогда не перезаписываются генератором классов.

Чаще всего вначале проекта вы создаёте классы из моделера, но на последующих этапах генерация классов обычно автоматизируется с помощью заданий Ant cgen или Maven cgen. Все эти три метода взаимозаменяемы, однако методы Ant и Maven гарантируют, что вы никогда не забудете пересоздать классы после изменения отображения, т.к. они интегрированы в цикл сборки.

Начало работы с классом ObjectContext в Cayenne

Вот мы и дошли, наконец, до программирования и до изучения Cayenne API. Сначала давайте создадим основной класс для запуска нашего приложения.

      • В Eclipse создайте новый класс Main в пакете «ru.proghouse.cayenne».
      • Создайте стандартный метод «main», чтобы сделать класс исполняемым:

package ru.proghouse.cayenne;
 
public class Main
{
      public static void main(String[] args)
      {
 
      }
}

      • Первое, что вы должны сделать для возможности подключения к базе данных, создать объект ServerRuntime (который, по сути, является обёрткой для стека Cayenne) и использовать его для получения экземпляра объекта ObjectContext.

package ru.proghouse.cayenne;
 
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.configuration.server.ServerRuntime;
 
public class Main
{
      public static void main(String[] args)
      {
            ServerRuntime cayenneRuntime = new ServerRuntime("cayenne-project.xml");
            ObjectContext context = cayenneRuntime.getContext();
      }
}

Объект ObjectContext является изолированной «сессией» в Cayenne, которая предоставляет весь необходимый инструментарий для работы с данными. Здесь есть методы для выполнения запросов и управления постоянными объектами. Когда объект ObjectContext создаётся в приложении в первый раз, Cayenne загружает XML-файл с отображением и создаёт общедоступный стек, который используют последующие создаваемые экземпляры объекта ObjectContext.

Теперь давайте посмотрим, что происходит при запуске приложения. Но прежде чем сделать это, нам нужно добавить другую зависимость в pom.xml - Apache Derby, движок для нашей встроенной базы данных. Добавьте следующий кусок XML в секцию <dependencies>...</dependencies>:

      <dependency>
         <groupId>org.apache.derby</groupId>
         <artifactId>derby</artifactId>
         <!-- Последнюю версию Apache Derby можно узнать на db.apache.org/derby   -->
         <version>10.11.1.1</version>
      </dependency>

Теперь всё готово к запуску. Щёлкните по классу «Main» правой кнопкой мышки и выберите пункт меню «Run As -> Java Application». В консоли вы увидите примерно следующие сообщения, означающие, что стек Cayenne был создан:

INFO: Loading XML configuration resource from file:/C:/workspace/example/target/classes/cayenne-project.xml
INFO: Loading XML DataMap resource from file:/C:/workspace/example/target/classes/datamap.map.xml
INFO: loading user name and password.
INFO: Created connection pool: jdbc:derby:memory:testdb;create=true
            Driver class: org.apache.derby.jdbc.EmbeddedDriver
            Min. connections in the pool: 1
            Max. connections in the pool: 1
INFO: setting DataNode 'datanode' as default, used by all unlinked DataMaps

Проверка и настройка постоянных объектов в Cayenne

Постоянные классы в Cayenne реализовывают интерфейс DataObject. Если вы посмотрите на любой из сгенерированных классов, например, ru.proghouse.cayenne.persistent.Artist, то вы увидите, что он наследуется от класса ru.proghouse.cayenne.persistent.auto._Artist, который наследуется от org.apache.cayenne.CayenneDataObject. Разбиение каждого постоянного класса на настраиваемый пользователем подкласс (Xyz) и генерируемый суперкласс (_Xyz) является полезным методом, чтобы избежать потери пользовательского кода, когда происходит обновление классов по модели отображения.

Давайте для примера добавим в класс Artist метод для установки даты рождения, который будет принимать дату в виде строки. Эта функция сохранится, если модель в будущем изменится.

package ru.proghouse.cayenne.persistent;
 
import java.util.Date;
import java.text.SimpleDateFormat;
import java.text.ParseException;
 
import ru.proghouse.cayenne.persistent.auto._Artist;
 
public class Artist extends _Artist
{
 
      static final String DEFAULT_DATE_FORMAT = "yyyyMMdd";
 
      /**
       * Устанавливает дату рождения используя строку формата yyyyMMdd.
       */
      public void setDateOfBirthString(String yearMonthDay)
      {
            if (yearMonthDay == null)
                  setDateOfBirth(null);
            else
            {
                  Date date;
                  try
                  {
                        date = new SimpleDateFormat(DEFAULT_DATE_FORMAT)
                                    .parse(yearMonthDay);
                  }
                  catch (ParseException e)
                  {
                        throw new IllegalArgumentException(
                                    "Дата должна быть указана в формате '"
                                    + DEFAULT_DATE_FORMAT + "': " + yearMonthDay);
                  }
                  setDateOfBirth(date);
            }
      }
}

Создание новых объектов

Сейчас мы создадим несколько объектов и сохраним их в базу данных. Создание и регистрация объекта производится с помощью метода «newObject» объекта ObjectContext. Объекты должны быть зарегистрированы с помощью DataContext, чтобы быть постоянными и чтобы иметь доступ к настройкам связей с другими объектами. Добавьте следующий код в метод «main» класса «Main»:

Artist picasso = context.newObject(Artist.class);
picasso.setName("Пабло Пикассо");
picasso.setDateOfBirthString("18811025");

Обратите внимание, что в этот момент объект «picasso» пока хранится только в памяти и ещё не сохранён в базе данных. Давайте продолжим, добавив музей имени Пушкина и пару картин Пикассо.

Gallery pushkin = context.newObject(Gallery.class);
pushkin.setName("Музей изобразительных искусств им. А.С. Пушкина"); 
 
Painting girl = context.newObject(Painting.class);
girl.setName("Девочка на шаре");
 
Painting violin = context.newObject(Painting.class);
violin.setName("Скрипка");

Теперь мы можем связать объекты друг с другом. Обратите внимание, что связь сразу устанавливается в обоих направлениях (например, picasso.addToPaintings(girl) создаст точно такую же связь, как и girl.setToArtist(picasso)).

picasso.addToPaintings(girl);
picasso.addToPaintings(violin);
 
girl.setGallery(pushkin);
violin.setGallery(pushkin);

Теперь сохраним все пять объектов, вызвав всего один метод:

context.commitChanges();

После этого вы можете запустить приложение так же, как было описано выше. В консоли вы увидите несколько операций с базой данных:

INFO: Loading XML configuration resource from file:/C:/workspace/example/target/classes/cayenne-project.xml
...
INFO: Opening connection: jdbc:derby:memory:testdb;create=true
            Login: null
            Password: *******
INFO: +++ Connecting: SUCCESS.
INFO: Detected and installed adapter: org.apache.cayenne.dba.derby.DerbyAdapter
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'GALLERY']
INFO: --- transaction started.
INFO: No schema detected, will create mapped tables
INFO: CREATE TABLE GALLERY (ID INTEGER NOT NULL, NAME VARCHAR (200) NOT NULL, PRIMARY KEY (ID))
INFO: CREATE TABLE ARTIST (DATE_OF_BIRTH DATE NOT NULL, ID INTEGER NOT NULL, NAME VARCHAR (200) NOT NULL, PRIMARY KEY (ID))
INFO: CREATE TABLE PAINTING (ARTIST_ID INTEGER NOT NULL, GALLERY_ID INTEGER, ID INTEGER NOT NULL, NAME VARCHAR (200) NOT NULL, PRIMARY KEY (ID))
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (ARTIST_ID) REFERENCES ARTIST (ID)
INFO: ALTER TABLE PAINTING ADD FOREIGN KEY (GALLERY_ID) REFERENCES GALLERY (ID)
INFO: CREATE TABLE AUTO_PK_SUPPORT (   TABLE_NAME CHAR(100) NOT NULL,   NEXT_ID BIGINT NOT NULL,   PRIMARY KEY(TABLE_NAME))
INFO: DELETE FROM AUTO_PK_SUPPORT WHERE TABLE_NAME IN ('ARTIST', 'GALLERY', 'PAINTING')
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('ARTIST', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('GALLERY', 200)
INFO: INSERT INTO AUTO_PK_SUPPORT (TABLE_NAME, NEXT_ID) VALUES ('PAINTING', 200)
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'ARTIST']
INFO: SELECT NEXT_ID FROM AUTO_PK_SUPPORT WHERE TABLE_NAME = ? FOR UPDATE [bind: 1:'PAINTING']
INFO: INSERT INTO GALLERY (ID, NAME) VALUES (?, ?)
INFO: [batch bind: 1->ID:200, 2->NAME:'Музей изобразительных искусств...']
INFO: === updated 1 row.
INFO: INSERT INTO ARTIST (DATE_OF_BIRTH, ID, NAME) VALUES (?, ?, ?)
INFO: [batch bind: 1->DATE_OF_BIRTH:'1881-10-25 00:00:00.0', 2->ID:200, 3->NAME:'Пабло Пикассо']
INFO: === updated 1 row.
INFO: INSERT INTO PAINTING (ARTIST_ID, GALLERY_ID, ID, NAME) VALUES (?, ?, ?, ?)
INFO: [batch bind: 1->ARTIST_ID:200, 2->GALLERY_ID:200, 3->ID:200, 4->NAME:'Девочка на шаре']
INFO: [batch bind: 1->ARTIST_ID:200, 2->GALLERY_ID:200, 3->ID:201, 4->NAME:'Скрипка']
INFO: === updated 2 rows.
INFO: +++ transaction committed.

Итак, давайте посмотрим, что делает Cayenne. Сначала он создаёт необходимые таблицы (потому, что мы используем «CreateIfNoSchemaStrategy»), первичные и вторичные ключи и добавляет записи. Совсем неплохо для нескольких строк кода.

Выборка объектов в Cayenne

Запросы Cayenne используются для доступа к сохранённым объектам. Основной способ выборки объектов – это SelectObject. Он может быть отображён в моделере Cayenne или через API. В примере ниже мы будем использовать второй подход. Данных у нас немного, но для демонстрации основных принципов этого достаточно.

Вот выборка всех объектов Painting:

SelectQuery select1 = new SelectQuery(Painting.class);
List paintings1 = context.performQuery(select1);

А вот что вы увидите в консоли:

INFO: SELECT t0.NAME, t0.ARTIST_ID, t0.GALLERY_ID, t0.ID FROM PAINTING t0 - prepared in 6 ms.
INFO: === returned 2 rows. - took 17 ms.

Теперь давайте сделаем выборку только тех объектов, имена которых начинаются на «де», игнорируя регистр:

Expression qualifier2 = ExpressionFactory.likeIgnoreCaseExp(
            Painting.NAME_PROPERTY,
            "де%");
SelectQuery select2 = new SelectQuery(Painting.class, qualifier2);
List paintings2 = context.performQuery(select2);

А вот, что будет в консоли:

INFO: SELECT t0.NAME, t0.ARTIST_ID, t0.GALLERY_ID, t0.ID FROM PAINTING t0 WHERE UPPER(t0.NAME) LIKE UPPER(?) [bind: 1->NAME:'де%'] - prepared in 6 ms.
INFO: === returned 1 row. - took 15 ms.

Теперь сделаем выборку картин тех художников, которые родились более 100 лет назад (здесь будем использовать Expression.fromString(..) вместо ExpressionFactory):

Calendar c = new GregorianCalendar();
c.set(c.get(Calendar.YEAR) - 100, 0, 1, 0, 0, 0);
 
Expression qualifier3 = Expression.fromString("artist.dateOfBirth < $date");
qualifier3 = qualifier3.expWithParameters(Collections.singletonMap("date", c.getTime()));
SelectQuery select3 = new SelectQuery(Painting.class, qualifier3);
List paintings3 = context.performQuery(select3);

А вот и текст из консоли:

INFO: SELECT t0.NAME, t0.ARTIST_ID, t0.GALLERY_ID, t0.ID FROM PAINTING t0 JOIN ARTIST t1 ON (t0.ARTIST_ID = t1.ID) WHERE t1.DATE_OF_BIRTH < ? [bind: 1->DATE_OF_BIRTH:'1915-01-01 00:00:00.273'] - prepared in 6 ms.
INFO: === returned 2 rows. - took 21 ms.

А теперь давайте сложим два условия с помощью логического «ИЛИ» (выберем картины начинающиеся на «де» или «ск»):

Expression qualifier4_1 = ExpressionFactory.likeIgnoreCaseExp(
           Painting.NAME_PROPERTY,
            "де%");
Expression qualifier4_2 = ExpressionFactory.likeIgnoreCaseExp(
            Painting.NAME_PROPERTY,
            "ск%");
Expression qualifier4 = ExpressionFactory.joinExp(
            Expression.OR,
            Arrays.asList(qualifier4_1, qualifier4_2));
SelectQuery select4 = new SelectQuery(Painting.class, qualifier4);
List paintings4 = context.performQuery(select4);

Два условия при этом сложатся в один запрос к базе данных:

INFO: SELECT t0.NAME, t0.ARTIST_ID, t0.GALLERY_ID, t0.ID FROM PAINTING t0 WHERE (UPPER(t0.NAME) LIKE UPPER(?)) OR (UPPER(t0.NAME) LIKE UPPER(?)) [bind: 1->NAME:'де%', 2->NAME:'ск%'] - prepared in 6 ms.
INFO: === returned 2 rows. - took 17 ms.

С помощью способа, показанного в последнем примере, вы можете строить сколь угодно сложные условия, создавая коллекции коллекций условий разной вложенности. В каждой коллекции условий, элементы могут складываться с помощью логических «И» или «ИЛИ»

Установка правил удаления в Cayenne

До того как мы разберёмся как нужно удалять объекты с помощью API, нам нужно вернуться в CayenneModeler и установить несколько правил удаления. Делать это не обязательно, но это облегчает обработку связанных объектов с удаляемыми. Выберите в моделере Cayenne объектную сущность «Artist», откройте закладку «Relationships» и установите для связи «paintings» правило удаления «Cascade» (Каскад, это значит, что при удалении объекта будут удалены все связи и связанные объекты, т.е. в нашем случае при удалении художника («Artist») будут удалены все его картины («Painting»)).

Установка правил удаления в Apache CayenneModeler

Повторите аналогичные шаги для остальных связей:

      • У «Gallery» для связи «paintings» выставьте «Nullify» (Аннулировать, это значит, что при удалении объекта удаляется (аннулируется) только связь, но все связанные объекты остаются, т.е. в нашем случае при удалении галереи («Gallery») все картины («Painting») останутся), ведь картина может существовать, но нигде при этом не выставляться.
      • У «Painting» для обеих связей выставьте «Nullify».

Ещё вы можете заметить, что в Cayenne есть правила удаления «Deny» и «No Action». Если для связи установлено правило удаления «Deny» (Отказать), то удаление объекта будет запрещено, пока он связан с другим объектом. В этом случае, сначала нужно удалить все связанные объекты, а затем уже удалить сам объект. Правило «No Action» (Нет действия), это, по сути, отсутствие правила и во многих случаях при удалении у вас будут происходить ошибки. Лучше не использовать «No Action». Всегда выбирайте одно из трёх правил удаления: «Deny», «Nullify» или «Cascade».

Теперь сохраните отображение и обновите проект в Eclipse.

Удаление объектов в Cayenne

Наиболее распространённый способ удаления объектов в Cayenne (и в целом в ORM) – это сначала получение объектов, а затем их удаление при помощи контекста. Давайте используем вспомогательный класс Cayenne для выборки объектов:

Expression qualifier = ExpressionFactory.matchExp(Artist.NAME_PROPERTY, "Pablo Picasso");
SelectQuery select = new SelectQuery(Artist.class, qualifier);
Artist picasso = (Artist) Cayenne.objectForQuery(context, select);

А теперь удалим художника:

if (picasso != null)
{
      context.deleteObject(picasso);
      context.commitChanges();
}

После того как мы установили правило удаления «Cascade» для связи «Artist.paintings», Cayenne будет автоматически удалять все картины удаляемого художника. Запустите приложение и посмотрите, что будет в консоли. Как видите, сначала удаляются картины, а потом художник.

INFO: SELECT t0.DATE_OF_BIRTH, t0.NAME, t0.ID FROM ARTIST t0 WHERE t0.NAME = ? [bind: 1->NAME:'Пабло Пикассо'] - prepared in 7 ms.
INFO: === returned 1 row. - took 15 ms.
INFO: +++ transaction committed.
INFO: --- transaction started.
INFO: DELETE FROM PAINTING WHERE ID = ?
INFO: [batch bind: 1->ID:200]
INFO: [batch bind: 1->ID:201]
INFO: === updated 2 rows.
INFO: DELETE FROM ARTIST WHERE ID = ?
INFO: [batch bind: 1->ID:200]
INFO: === updated 1 row.
INFO: +++ transaction committed.

Подводим итоги

Надеюсь, теперь вы почувствовали, что значит работать с объектной базой данных? Удобно, правда? А в следующей статье, посвящённой Apache Cayenne, мы создадим веб-приложение и научим работать его с СУБД ЛИНТЕР.

Tags: Apache Cayenne Обзоры инструментов для программирования Учебники по программированию

Добавить комментарий