Содержание


Java development 2.0

Часть 2. Вторая волна разработки Java-приложений: «Облачное» хранилище средствами Amazon SimpleDB

Реализация долговременного хранения простых объектов в SimpleJPA

Comments

Серия контента:

Этот контент является частью # из серии # статей: Java development 2.0

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Java development 2.0

Следите за выходом новых статей этой серии.

В части 1 нашего знакомства с SimpleDB для моделирования CRUD-приложения, обрабатывающего данные о соревнованиях и их участниках, мы использовали API, разработанный компанией Amazon. Помимо непривычного для большинства Java-разработчиков решения Amazon использовать только строковые данные, сам по себе Amazon API, скорее всего, не вызвал у вас особенного восторга. В конце концов, современные API для работы с реляционными базами данных достаточно стандартизированы и продуманы, а самое главное, хорошо изучены.

Большинство современных реляционных инфраструктур включают в себя реализацию Java Persistence API. Благодаря этой особенности моделирование доменных объектов для Java-приложений практически любого типа в случае целого ряда реляционных СУБД превращается в легко решаемую и хорошо изученную задачу. Вполне понятно нежелание разработчиков использовать новые подходы и средства доменного моделирования, когда в их распоряжении уже имеются хорошо знакомые и работающие инструменты. В случае SimpleDB ничего нового изучать и не придется.

Во второй части моего обзора SimpleDB я расскажу вам, как с помощью рефакторинга можно обеспечить совместимость нашего приложения для хранения данных о соревнованиях со спецификацией JPA. Затем мы перенесем это приложение на SimpleJPA и рассмотрим несколько полезных особенностей этой инновационной открытой платформы, которые помогают быстрее адаптироваться к принципам NoSQL, упрощают процесс доменного моделирования и организацию облачных хранилищ.

Hibernate и JPA: краткий экскурс в историю

Большинство современных разработчиков Java используют для обеспечения долговременного хранения объектов данных Hibernate (и Spring). Hibernate не только занимает ведущее место в списке инфраструктур с открытым кодом - он практически произвел переворот в сфере ORM. До появления Hibernate разработчики Java утопали в болоте объектных компонентов EJB; нам приходилось либо разрабатывать свое собственное ORM, либо покупать готовые решения у таких вендоров, как IBM®. С появлением Hibernate все эти проблемы и затраты ушли в прошлое, уступив место POJO-платформе моделирования, которую современные разработчики уже воспринимают как данность.

Java Persistence API (JPA) появился в ответ на популярность инновационного решения Hibernate по использованию POJO (Plain old Java objects) для моделирования данных. В настоящее время JPA поддерживается в версии EJB 3.0 и в Google App Engine. Собственно, и сам Hibernate является реализацией JPA (в том случае, если вы используете Hibernate EntityManager).

Учитывая, насколько Java-разработчики привыкли использовать POJO при моделировании приложений для работы с данными, можно предположить, что аналогичная функциональность реализована и в SimpleDB. В конце концов, это же своего рода база данных, не правда ли?

Моделирование данных с помощью объектов

Для использования SimpleJPA нам придется пересмотреть наше определение объектов класса Racer и Runner с точки зрения спецификации JPA. К счастью, основы JPA достаточно просты: вы добавляете к POJO-объектам аннотации, а реализация EntityManager позаботится обо всем остальном – и не нужно никакого XML.

Две основные аннотации, используемые JPA, - это @Entity и @Id, которые соответственно определяют POJO как постоянно хранимый объект и задают его идентификационный ключ. Кроме того, для переноса нашего приложения на JPA нам потребуются еще две аннотации, используемые для управления взаимосвязями между объектами: @OneToMany и @ManyToOne.

В первой части данной статьи мы научились сохранять соревнования и их участников. При этом я не использовал объекты для представления этих сущностей, мы просто воспользовались для сохранения их свойств средствами Amazon API. Если бы мне потребовалось смоделировать взаимосвязи между соревнованиями и их участниками, я мог бы сделать это с помощью кода, приведенного в листинге 1:

Листинг 1. Простой объект Race
public class Race {
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;
	
 //методы-установщики и методы-получатели здесь пропущены 
}

В листинге 1 я описал объект Race, обладающий четырьмя свойствами, последнее из которых – коллекция участников соревнований. После этого я могу создать объект Runner (см. листинг 2), в котором будет храниться имя участника (упростим задачу), его номер социального страхования и соревнование, в котором участвует данный спортсмен.

Листинг 2. Простой объект Runner, связанный с объектом Race
public class Runner  {
 private String name;
 private String ssn;
 private Race race;

 //методы-установщики и методы-получатели здесь пропущены 

}

Как показано в листингах 1 и 2, я логически определил модель связи между бегунами и соревнованиями как много-к-одному. В реальной модели следовало бы создать связи типа много-ко-многим (поскольку один и тот же спортсмен, как правило, участвует в нескольких соревнованиях), но мы пока не будем усложнять задачу. Кроме того, я опустил конструкторы, методы-установщики и методы-получатели. Мы включим их в код чуть позже.

Аннотации в JPA

Адаптация созданных выше объектов к реализации SimpleJPA не потребует значительных усилий. Сначала нужно указать, что объекты должны храниться постоянно. Для этого я добавлю к каждому из объектов аннотацию @Entity. Кроме того, следует определить взаимосвязи между объектами с помощью аннотации @OneToMany для объекта Race и @ManyToOne для объекта Runner.

Аннотация @Entity применяется на уровне класса, в то время как аннотации отношений используются на уровне методов-получателей. Все вышесказанное продемонстрировано в листингах 3 и 4:

Листинг 3. Объект Race с JPA-аннотацией
@Entity
public class Race {
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //остальные методы-установщики и методы-получатели здесь пропущены 
}

В листинге 3 метод getRunners имеет аннотацию @OneToMany. Дополнительно указывается, что связь определяется свойством race объекта Runner.

Аналогичным образом аннотируется метод getRace объекта Runner (см. листинг 4):

Листинг 4. Объект Runnerс JPA-аннотацией
@Entity
public class Runner  {
 private String name;
 private String ssn;
 private Race race;

 @ManyToOne
 public Race getRace() {
  return race;
 }

 //other setters and getters left out...
}

Большинство хранилищ (как реляционных, так и нереляционных) требуют какого-либо способа задания уникальности данных. Таким образом, если мне нужно хранить эти два объекта в базе данных постоянно, я должен как минимум добавить каждому из них уникальный идентификатор. Листинг 5 добавляет доменному объекту Race свойство id типа BigInteger. Точно такую же операцию я применю к объекту Runner.

Листинг5. Добавление ID к объекту Race
@Entity
public class Race {
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;
 private BigInteger id;

 @Id
 public BigInteger getId() {
  return id;
 }

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //остальные методы-установщики и методы-получатели здесь пропущены 
}

Аннотация @Id в листинге 5 не дает никакой информации об управлении ID. Программа предполагает, что все манипуляции над этим свойством я буду проводить самостоятельно, вручную, без использования EntityManager.

Переходим к SimpleJPA

До сих пор все мои операции никак не были связаны с SimpleDB. Объекты Race и Runner с JPA-аннотациями могут постоянно храниться в произвольной базе данных, поддержка которой включена в реализацию JPA. Список подобных баз включает в себя Oracle, DB2, MySQL и, как вы, вероятно, уже догадались, SimpleDB.

SimpleJPA – это открытая реализация JPA для Amazon SimpleDB. Несмотря на то, что SimpleJPA не поддерживает полностью спецификацию JPA (например, в JPA-запросах нельзя использовать join), объем поддерживаемой функциональности достаточно велик и заслуживает отдельного исследования.

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

Поскольку SimpleJPA - это реализация JPA, то при работе с SimpleJPA вы можете использовать JPA-совместимые домены. Единственное требование при этом – использовать строковые идентификаторы объектов. Это означает, что в вашей модели свойство id должно иметь тип java.lang.String. Чтобы упростить задачу, SimpleJPA включает базовый класс IdedTimestampedBase, который используется для задания ID доменных объектов, а также для создания и обновления атрибутов времени (по сути, SimpleDB создает уникальный идентификатор объекта).

Перенос приложений на SimpleJPA

Для обеспечения совместимости классов Race и Runner с SimpleJPA я могу либо расширить весьма удобный базовый класс, либо изменить тип идентификатора классов Race и Runner с BigInteger на String. Я предпочитаю воспользоваться первым вариантом (см. листинг 6):

Листинг 6. Преобразование класса Race в расширение базового класса SimpleJPA IdedTimestampedBase
@Entity
public class Race extends IdedTimestampedBase{
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //остальные методы-установщики и методы-получатели здесь пропущены 
}

Я не привожу здесь аналогичное преобразование класса Runner - вы вполне можете выполнить его самостоятельно. Все, что вам нужно сделать, - это расширить базовый класс IdedTimestampedBase и убрать объявление свойства id класса Runner.

Изменение типа идентификаторов классов Race и Runner – это первый шаг в цепочке преобразований, необходимых для обеспечения совместимости нашего приложения с SimpleJPA. Следующий шаг – преобразование базовых типов данных, таких как double, int и float, к типам Integer и BigDecimal.

Начнем со свойства distance объектов класса Race. По моему мнению, тип BigDecimal более удобен в использовании, чем тип Double (по крайней мере в текущей реализации SimpleJPA), так что я изменю тип distance на BigDecimal (см. листинг 7):

Листинг 7. Изменение типа distance на BigDecimal
@Entity
public class Race extends IdedTimestampedBase{
 private String name;
 private String location;
 private BigDecimal distance;
 private List<Runner> runners;

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //остальные методы-установщики и методы-получатели здесь пропущены 
}

Теперь оба класса Runner и Race могут использоваться для постоянного хранения объектов в реализации SimpleJPA.

Использование SimpleJPA для работы с SimpleDB

Работа с доменными объектами в хранилище SimpleDB с помощью SimpleJPA мало чем отличается от работы с обычной реляционной базой данных с помощью реализации JPA. Если у вас есть опыт разработки приложений средствами JPA, то вряд ли вас ожидают какие-нибудь сюрпризы. Единственная операция, которая может оказаться незнакомой, - это конфигурирование SimpleJPA EntityManagerFactoryImpl (для выполнения этого шага необходимо указать данные вашей учетной записи в Amazon Web Services и префиксное имя домена в SimpleDB; в качестве альтернативного решения можно использовать файл свойств, содержащий данные учетной записи в пути поиска классов classpath).

Указание префиксного имени при создании экземпляра SimpleJPA EntityManagerFactoryImpl определяет домен SimpleDB, имя которого начинается с вашего префикса, за которым через черточку указываются имена доменных объектов. Так, например, если я в качестве префиксного имени указал "b50", то при создании элемента Race в SimpleDB имя соответствующего доменного объекта будет "b50-Race".

После создания экземпляра SimpleDB EntityManagerFactoryImpl все остальные операции осуществляются посредством функций интерфейса. Вам потребуется экземпляр EntityManager, который можно создать с помощью EntityManagerFactoryImpl (см. листинг 8):

Листинг 8. Создание экземпляра EntityManager
Map<String,String> props = new HashMap<String,String>();
props.put("accessKey","...");
props.put("secretKey","..");

EntityManagerFactoryImpl factory = 
  new EntityManagerFactoryImpl("b50", props);

EntityManager em = factory.createEntityManager();

Работа с доменными объектами

После того как вы создали экземпляр EntityManager, вы можете свободно манипулировать доменными объектами. Например, я могу создать объект класса Race следующим образом:

Листинг 9. Создание объекта Race
Race race = new Race();
race.setName("Charlottesville Marathon");
race.setLocation("Charlottesville, VA");
race.setDistance(new BigDecimal(26.2));
em.persist(race);

В листинге 9 SimpleJPA обрабатывает HTTP-запрос на создание в облаке объекта класса Race. SimpleJPA также позволяет мне извлечь элемент race с помощью JPA-запроса (см. листинг 10). Еще раз хочу напомнить, что мы не можем использовать операцию объединения в подобных запросах; тем не менее мы можем проводить поиск по числовым значениям.

Листинг 10. Поиск соревнований по длине дистанции
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
		
List<Race> races = query.getResultList();
for(Race race : races){
 System.out.println(race);
}

От чисел к строкам

Одним из основных достоинств SimpleJPA является скрытый механизм преобразования числовых значений в строковые. Если вы включите в код распечатку запросов, формируемых SimpleJPA, вы увидите ваш запрос в том виде, в котором он передается базе данных SimpleDB. Окончательный вид запроса приведен в листинге 11 – обратите внимание на преобразование числового значения длины дистанции.

Листинг 11. Магическое преобразование числового параметра средствами SimpleJPA
amazonQuery: Domain=b50-Race, query=select * from `b50-Race` 
  where `distance` = '0922337203685477583419999999999999928946'

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

Взаимосвязи в SimpleJPA

Несмотря на то, что SimpleDB не поддерживает запросы с кросс-доменным объединением, вы тем не менее можете использовать взаимосвязи между объектами разных доменов. Например, вы можете хранить ключевой индекс связанного объекта в другом объекте, а потом извлекать нужный вам связанный объект по значению ключа (мы обсуждали такой подход в части 1. Аналогичный подход реализован и в SimpleJPA. Ранее я продемонстрировал вам, как можно связать классы Runners и Race посредством JPA-аннотаций. Таким образом, я могу создать элемент класса Runner, добавить в него указание на существующий элемент race, а потом сохранить объект Runner, как показано в листинге 12:

Листинг 12. Определение взаимосвязи объектов в SimpleJPA
Runner runner = new Runner();
runner.setName("Mark Smith");
runner.setSsn("555-55-5555");
runner.setRace(race);
race.addRunner(runner);
		
em.persist(runner);
em.persist(race); 
//Сохраняем измененный объект race, к которому теперь «привязан» участник runner

Обратите внимание, в листинге 12 я сохраняю измененный объект класса Race, поскольку я добавил к нему объект класса Runner. Кроме того, я добавил к классу Race новый метод addRunner, который позволяет добавлять новый объект Runner в список участников соревнования.

Если я теперь выполню поиск соревнований по длине дистанции, в результат выполнения запроса будет включен список участников соревнований (см. листинг 13):

Листинг 13. Еще одно преимущество использования взаимосвязей
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
		
List<Race> races = query.getResultList();
		
for(Race races : race){
 System.out.println(race);
 List<Runner> runners = race.getRunners();
 for(Runner rnr : runners){
  System.out.println(rnr);
 }
}

Использование экземпляра EntityManager позволяет мне удалять объекты с помощью метода remove, как показано в листинге 14:

Листинг 14. Удаление объекта класса Race
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
		
List<Race> races = query.getResultList();
		
for(Race races : race){
 em.remove(race);
}

При удалении объекта Race, как показано в листинг 14, связанные с ним объекты Runner не удаляются (я, конечно, могу изменить это поведение с помощью JPA-аннотации EntityListeners, т.е. я могу подключиться к процессу удаления и удалить соответствующие объекты Runner).

В заключение

В ходе нашего ускоренного тура по SimpleDB я рассказал вам, как оперировать объектами в нереляционном хранилище с помощью API семейства веб-сервисов Amazon и интерфейса SimpleJPA. Simple JPA представляет собой реализацию подмножества Java Persistence API и позволяет без особых проблем обеспечить постоянное хранение объектов в SimpleDB. Как вы уже убедились, одно из основных преимуществ использования SimpleJPA состоит в том, что он поддерживает автоматическое преобразование объектов базовых типов в строковые объекты, которыми оперирует SimpleDB. Кроме того, SimpleJPA учитывает отсутствие объединений в SimpleDB, что сильно облегчает процесс моделирования взаимосвязей данных. Расширенные listener-интерфейсы SimpleJPA обеспечивают реализацию правил логической целостности данных, - еще одного свойства, которое является привычным и ожидаемым для реляционного мира.

Основной итог нашего знакомства с SimpleJPA таков: этот интерфейс позволяет быстро и без особых проблем получить доступ к широким возможности масштабирования по минимальной цене. Благодаря SimpleJPA вы сможете эффективно использовать ваши знания и опыт, накопленные в течение нескольких лет работы с инфраструктурами типа Hibernate, для работы в нереляционной среде облачного хранилища.


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=775340
ArticleTitle=Java development 2.0: Часть 2. Вторая волна разработки Java-приложений: «Облачное» хранилище средствами Amazon SimpleDB
publish-date=11162011