Основные понятия
В Grails активно применяется концепция Domain Driven Development, моделирующая поведение системы с помощью доменных классов. При создании приложения Grails-архитектор создает доменные классы и указывает связи между ними, а GORM сама создает базу данных и соответствующие доменным классам таблицы.При использовании ORM, программист работает преимущественно с доменными объектами, система сама формирует SQL-код, взаимодействует с реляционной БД, преобразуя результаты, полученные в форме SQL, обратно в доменныхе объекты. Если разделить систему на абстрактные уровни, слой GORM находится на уровень выше слоя Hibernate, а под слоем Hibernate находится слой работы с базой данных, общающийся с ней через SQL-запросы.
После ознакомления с примерами, рассмотренными в руководстве пользователя, у программиста может сложиться обманчивое впечатление о чрезычайной простоте проектирования базы данных и запросов к ней с помощью GORM. Программист может работать с объектами доменных классов через GORM и иметь довольно туманные представления о лежащих в её основе технологиях Hibernate и SQL. Действительно, для простейших случаев это так. Но, при проектировании крупных веб-приложений с десятками доменных классов ситуация становится совершенно иной. Профессиональному Grails-архитектору необходимо в подробностях знать, как ведет себя GORM в тех или иных ситуациях и какой SQL-код генерирует система при любом обращении к базе данных.
В данной статье мы будем подробно рассматривать все виды связей в GORM. Для каждого типа связи будет приведен анализ SQL-кода, сгенерированного СУБД MySQL. Целью данной статьи является выяснение, как правильно строить связи между объектами в различных случаях.
Отношение "Один-ко-многим"
Отношение "Один-ко-многим" в GORM можно реализовать несколькими способами:1. Однонаправленная связь
public class Company {
static hasMany = [employees: Employee]
}
public class Employee {
}
В данном варианте отношения, с точки зрения объектой модели, класс Employee не содержит ссылку на класс Company. Каскадное сохранение, которое доступно для данного отношения, позволяет сохранять в базу объект класса Company одновременно с несколькими ссылающимися на него объектами класса Employee.В базе данных создается 3 таблицы: company, employee и company_employee. Как видно из SQL-кода, таблица employee сама по себе не содержит ссылок на company.Если мы добавляем к компании сотрудника, запись об этом проcто добавляется в таблицу company_employee.
2. Двунаправленная связь
public class Company {
static hasMany = [employees: Employee]
}
public class Employee {
Company company
}
В отличие от однонаправленной связи, у класса Employee присутствует ссылка на класс Company. Каскадное сохранение точно также действует со стороны класса Company. Каскадное удаление со стороны класса Company запрещено3. Двунаправленная связь ключевым словом belongsTo
public class Company {
static hasMany = [employees: Employee]
}
public class Employee {
static belongsTo = [company : Company]
}
Данный вид отношения "один-ко-многим" предоставляет максимальное число возможностей по сравнению с предыдущими. Здесь так же работает каскадное сохранение и ,в отличие от предыдущего варианта, работает каскадное удаление со стороны объекта, содежащего hasMany. Это происходит благодаря добавлению ключевого слова belongsTo.Отношение "Многие-к-одному"
public class Company {
}
public class Employee {
Company company
}
Достаточно популярное и простое отношение, реализующее ссылку одного объекта на другой.Отношение "Один-к-одному"
Отношение "Один-ко-одному" можно также реализовать несколькими способами:1. Однонаправленная связь c ключевым словом belongsTo, расположенным с подчиненной стороны
class Face {
}
class Nose {
static belongsTo = [face : Face]
}
Таблица nose ссылается на таблицу face с помощью поля face_id. Объектом-владельцем выступает объект класса Face. Для работы с обоими объектами пользователь должен сначала создать класс Face, потом класс Nose. Несмотря на то, что для “объекта-владельца” Face доступно каскадное сохранение, выпонить его невозможно, так как в описании класса Face отсутствует ссылка на объект класса Nose.SQL:код
CREATE TABLE `face` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`) ) ENGINE = InnoDB; CREATE TABLE `nose` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, `face_id` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`), /* Foreign keys */ CONSTRAINT FK33AFD3BF056CBA FOREIGN KEY (`face_id`) REFERENCES `face`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB; CREATE INDEX FK33AFD3BF056CBA ON `nose` (`face_id`);
2. Однонаправленная связь с использованим атрибута unique
class Face {
Nose nose
static constraints = {
nose unique:true
}
}
class Nose {
}
Так как класс Face содержит ссылку на Nose, порядок создания объектов будет следующий: сначала необходимо создать и сохранить в базу объект класса Nose, а потому уже объект класса Face. Недостаток подобной модели – отсутствие каскадного сохранения.SQL:код
CREATE TABLE `nose` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`) ) ENGINE = InnoDB; CREATE TABLE `face` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, `nose_id` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`), /* Foreign keys */ CONSTRAINT FK2FD65D7F0070FA FOREIGN KEY (`nose_id`) REFERENCES `nose`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB; CREATE INDEX FK2FD65D7F0070FA ON `face` (`nose_id`); CREATE UNIQUE INDEX `nose_id` ON `face` (`nose_id`);
3. Двунаправленная без ключевого слова belongsTo
С точки зрения описания структуры базы данных доменными объектами, данная конструкция правильна, но её использование невозможно. Дело в том, что при таком объявлении, каждый объект ссылается на другой и ни один из них без другого создать не удастся. Чтобы создать объект класса Face, необходимо сначала создать объект nose и передать ему ссылку. И наоборот, чтобы создать объект класса Nose, необходимо создать объект класса Face и передать объекту класса Nose ссылку на него. Таким образом, мы попадаем в замкнутый круг, выйти из которого можно только благодаря каскадному сохранению. Чтобы обеспечить каскадное сохранение, в описание одного из классов необходимо добавить ключевое слово belongsTo.class Face {
Nose nose
}
class Nose {
Face face
}
4. Двунаправленная c ключевым словом belongsTo
class Face {
Nose nose
}
class Nose {
static belongsTo = [face:Face]
}
Применение конструкции belongsTo позволяет осуществлять каскадное сохранение со стороны объекта класса Face.SQL:код
CREATE TABLE `nose` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`) ) CREATE TABLE `face` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, `nose_id` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`), /* Foreign keys */ CONSTRAINT FK2FD65D7F0070FA FOREIGN KEY (`nose_id`) REFERENCES `nose`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB; CREATE INDEX FK2FD65D7F0070FA ON `face` (`nose_id`);
5. Двунаправленная с ключевым словом hasOne
class Face {
static hasOne = [nose:Nose]
}
class Nose {
Face face
}
SQL:кодCREATE TABLE `face` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`) ) ENGINE = InnoDB; CREATE TABLE `nose` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, `face_id` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`), /* Foreign keys */ CONSTRAINT FK33AFD3BF056CBA FOREIGN KEY (`face_id`) REFERENCES `face`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB; CREATE UNIQUE INDEX `face_id` ON `nose` (`face_id`); CREATE INDEX FK33AFD3BF056CBA ON `nose` (`face_id`);
Связь "Многие-ко-многим"
В качестве примера для моделирования приведена связка "Доктор"-"Пациент". Предполагается, что один пациент может лечиться у нескольких докторов и один доктор может иметь несколько пациентов.class Doctor {
static hasMany = [patients : Patient]
}
class Patient {
static hasMany = [doctors : Doctor]
static belongsTo = Doctor
}
Обратим внимание, что ключевое слово belongsTo применяется для того, чтобы указать какой из классов будет играть роль "владельца". В данном случаем, владельцем назначен класс Doctor. Со стороны объекта класса Doctor можно осуществлять каскадное сохранение и удаление. Со стороны объекта Patient каскадные операции не действуютSQL:код
CREATE TABLE `patient` ( `id` bigint AUTO_INCREMENT NOT NULL, `version` bigint NOT NULL, /* Keys */ PRIMARY KEY (`id`) ) ENGINE = InnoDB; CREATE TABLE `doctor_patient` ( `doctor_patients_id` bigint, `patient_id` bigint, /* Foreign keys */ CONSTRAINT FK667B93E544DC3A2F FOREIGN KEY (`patient_id`) REFERENCES `patient`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT FK667B93E564A4D9B6 FOREIGN KEY (`doctor_patients_id`) REFERENCES `doctor`(`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB; CREATE INDEX FK667B93E544DC3A2F ON `doctor_patient` (`patient_id`); CREATE INDEX FK667B93E564A4D9B6 ON `doctor_patient` (`doctor_patients_id`);
Советы при проектировании БД с помощью GORM
Ниже приведены советы по проектированию базы данных, исходя из практического опыта автора.
Слово belongsTo означает разное поведение объектов при разных типах связей. В связях "один-ко-многим" оно добавляет возможность каскадного удаления, а в связях "один-к-одному" возможность каскадного сохранения.
Если каскадное удаление в связке “один-ко-многим” не требуется, то ключевое слово belongsTo на стороне "дочерней сущности" можно не указывать
Комментариев нет:
Отправить комментарий