jActiveRecord — это библиотека объектно-реляционного отображения (ORM), написанная на Java по мотивам ActiveRecord из Ruby on Rails.
Особенности jActiveRecord:
В качестве примера создадим систему зомби-твитов, используя jActiveRecord.
jActiveRecord поддерживается Maven и доступен в центральном репозитории. Чтобы использовать его, добавьте следующую зависимость в файл pom.xml:
<dependency>
<groupId>me.zzp</groupId>
<artifactId>jactiverecord</artifactId>
<version>2.3</version>
</dependency>
Вход в jActiveRecord — класс DB. Он создаёт объект базы данных через метод open. Метод open принимает параметры, совместимые с java.sql.DriverManager#getConnection.
Пример создания объекта базы данных SQLite в памяти:
DB sqlite3 = DB.open("jdbc:sqlite::memory:");
Метод open по умолчанию создаёт только одно соединение с базой данных, поэтому он подходит для баз данных в памяти. В реальных проектах рекомендуется использовать пул соединений, например C3P0.
Создаём таблицу пользователей (зомби) с двумя полями: name (имя) и graveyard (место захоронения).
Table Zombie = sqlite3.createTable("zombies", "name text", "graveyard text");
Аргументы метода createTable описывают поля таблицы. Они должны быть в формате «имя поля + тип».
createTable автоматически добавляет поле id как первичный ключ. Если ваша база данных не поддерживается jActiveRecord, вы можете реализовать интерфейс Dialect и добавить его в META-INF/services/me.zzp.ar.d.Dialect. jActiveRecord использует ServiceLoader для автоматической загрузки реализаций Dialect.
Также jActiveRecord добавляет два поля created_at и updated_at типа timestamp для хранения времени создания и обновления записи. Таким образом, таблица содержит пять полей: id, name, graveyard, created_at и updated_at.
Получаем объект таблицы и добавляем записи с помощью метода create. Можно использовать именованные параметры для ясности.
Table Zombie = sqlite3.active("zombies");
Zombie.create("name:", "Ash", "graveyard:", "Glen Haven Memorial Cemetery");
Zombie.create("name", "Bob", "graveyard", "Chapel Hill Cemetery");
Zombie.create("graveyard", "My Fathers Basement", "name", "Jim");
Поскольку в Java нет именованных параметров, имена столбцов могут заканчиваться двоеточием. Это позволяет использовать «name:» вместо «name». Порядок аргументов не имеет значения, поэтому третья запись с именем Jim также будет успешно добавлена.
jActiveRecord предоставляет следующие методы запроса:
Методы first, last и find возвращают одну запись, а остальные методы могут возвращать несколько записей.
Например, чтобы получить запись с id = 3, можно использовать любой из следующих методов:
Zombie.find(3);
Zombie.findA("name", "Jim");
Zombie.first("graveyard like ?", "My Father%");
Записи, возвращаемые базой данных, представлены объектами Record. Для доступа к данным используется метод get. Благодаря дженерикам, данные автоматически преобразуются в соответствующий тип:
Record jim = Zombie.find(3);
int id = jim.get("id");
String name = jim.get("name");
Timestamp createdAt = jim.get("created_at");
Кроме того, Record предоставляет интерфейсы для принудительного преобразования типов, такие как getInt и getStr.
jActiveRecord не использует Bean, так как они не универсальны и требуют создания отдельного класса для каждой таблицы. Кроме того, использование Bean не даёт никаких преимуществ, кроме проверки правильности имён геттеров и сеттеров во время компиляции.
Чтобы обновить данные, сначала нужно найти нужную запись. Затем можно изменить значения и сохранить изменения.
Для обновления значений используется метод set, а для сохранения изменений — save или update. Также можно вызвать метод update, который выполняет обновление и сохранение за один шаг. Этот метод принимает именованные аргументы, как и create.
Record jim = Zombie.find(3);
jim.set("graveyard", "Benny Hills Memorial").save();
jim.update("graveyard:", "Benny Hills Memorial"); // Same with above
Эти строки кода обновляют место захоронения зомби с id = 3 до «Benny Hills Memorial».
Для удаления записей используются методы delete и destroy. Первый удаляет запись по её идентификатору, а второй — по объекту Record. Также есть метод purge для удаления всех записей, соответствующих текущим условиям.
Zombie.find(1).destroy();
Zombie.delete(Zombie.find(1)); // Same with above
Обе строки удаляют зомби с id = 1.
Наконец, переходим к связыванию данных. ORM-библиотеки не только отображают записи в объекты, но и связывают таблицы между собой.
jActiveRecord поддерживает четыре вида связей, аналогичных ActiveRecord: belongsTo, hasOne, hasMany и hasAndBelongsToMany. Каждый метод получает строку-параметр name в качестве имени отношения и возвращает объект ассоциации Association, который имеет три метода:
Возвращаясь к проблеме системы мёртвых аккаунтов в социальных сетях, в предыдущем разделе была создана только одна таблица пользователей, теперь создаётся другая таблица tweets для хранения информации о сообщениях:
Table Tweet = sqlite3.createTable("tweets", "zombie_id int", "content text");
Здесь zombie_id выступает в роли внешнего ключа и связан с id таблицы zombies. Таким образом, у каждого мёртвого аккаунта может быть много связанных сообщений, а каждое сообщение связано только с одним мёртвым аккаунтом. В jActiveRecord используются hasMany и belongsTo для описания таких отношений «один ко многим». hasMany используется на стороне «один», belongsTo — на стороне «многие» (то есть на стороне внешнего ключа).
Zombie.hasMany("tweets").by("zombie_id");
Tweet.belongsTo("zombie").by("zombie_id").in("zombies");
Затем можно получить связанные объекты через имена связей из Record. Например, чтобы получить все сообщения Джима:
Record jim = Zombie.find(3);
Table jimTweets = jim.get("tweets");
for (Record tweet : jimTweets.all()) {
// ...
}
Или получить информацию о мёртвом аккаунте по сообщению:
Record zombie = Tweet.find(1).get("zombie");
Возможно, вы заметили, что hasMany возвращает несколько записей, поэтому возвращается тип Table; belongsTo всегда возвращает одну запись, поэтому возвращается тип Record. Кроме того, существует особый вид отношений «один ко многим»: hasOne, то есть сторона «многие» имеет и может иметь только одну запись. hasOne используется так же, как hasMany, но возвращает значение типа Record, а не Table.
Давайте добавим функцию комментариев к системе сообщений:
Table Comment = sqlite3.createTable("comments", "zombie_id int", "tweet_id", "content text");
Одно сообщение может получить много комментариев, а один мёртвый аккаунт может иметь много сообщений. Поэтому мёртвые аккаунты и полученные комментарии связаны комбинацией: мёртвый аккаунт hasMany сообщений hasMany комментариев. jActiveRecord предоставляет through для описания этой комбинации связей.
Zombie.hasMany("tweets").by("zombie_id"); // has defined above
Zombie.hasMany("receive_comments").by("tweet_id").through("tweets");
Zombie.hasMany("send_comments").by("zombie_id").in("comments");
Приведённое выше правило описывает, что Zombie сначала может найти Tweet, а затем, используя Tweet.tweet_id, может найти Comment. Третья строка кода описывает, как Zombie может напрямую получать отправленные комментарии через Comment.zombie_id.
Фактически, through можно использовать для связывания любых типов связей, например, hasAndBelongsToMany зависит от hasOne или belongsTo зависит от другого belongsTo...
В RoR существует два метода для многих ко многим связям: has_many through и has_and_belongs_to_many, которые частично перекрываются по функциям. jActiveRecord сохраняет только метод hasAndBelongsToMany для описания многих ко многим связей. Для многих ко многим связей требуется отдельная таблица сопоставления, которая записывает отношения сопоставления. То есть две стороны «многие» не содержат внешних ключей друг друга, а используют третью таблицу для одновременного сохранения их внешних ключей.
Например, добавьте информацию о городе для каждого сообщения, и город будет отдельной таблицей.
sqlite3.dropTable("tweets");
Tweet = sqlite3.createTable("tweets", "zombie_id int", "city_id int", "content text");
Table City = sqlite3.createTable("cities", "name text");
Таблица cities содержит информацию обо всех городах, а таблица tweets записывает связь между мёртвыми аккаунтами и городами. Чтобы найти города, где побывал мёртвый аккаунт, Zombie должен сначала подключиться к таблице tweets, а затем использовать её для доступа к городам.
Zombie.hasMany("tweets").by("zombie_id"); // has defined above
Zombie.hasAndBelongsToMany("travelled_cities").by("city_id").in("cities").through("tweets");
Как следует из названия, тип возвращаемого значения для многих ко многим связей всегда Table, а не Record.
Через можно комбинировать другие связи произвольным образом.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )