При работе с Hibernate и другими ОРМ принято придерживаться принципа глубокой детализации предметной области (fine-grained domain model), что обычно означает, что классов с данными в приложении должно быть больше, чем таблиц в базе данных. Это достигается за счёт разделения классов на сущности и значения.
Представим следующий класс для работы с данными:
1 2 3 4 5 6 7 |
public class User { private Long id; private String name; private String address; private LocalDate birthday; private String email; } |
В базе ему соответствует таблица USERS:
ID | NAME | ADDRESS | BIRTHDAY | |
1 | Иванов Иван Иванович | г. Москва, ул. Садовая, д.1, кв.2 | 1980-01-01 | ivanov@ivanov.com |
Совершенно очевидно, что класс предметной области User содержит данные в несколько «скомканном» виде. Во-первых, имя пользователя надо разделить на ФИО. Если мы будем отправлять пользователю письмо, то обращаться мы к нему должны по имени-отчеству, без фамилии. Во-вторых, адрес тоже надо бы разбить как минимум на город и всё остальное.
Проведём рефакторинг, выделив два новых класса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Name { private String firstname; private String middleName; private String lastname; } class Address { //private Long id; private String city; private String location; } public class User { private Long id; private Name name; private Address address; private LocalDate birthday; private String email; } |
Мы глубже детализировали предметную область. Теперь нам надо определиться с тем, как зарефакторить таблицы базы данных. Для этого нам нужно определить, какие из этих трёх классов являются полноценными сущностями, а какие — значениями.
Сущности (entities, на примере класса User):
- Обязательно имеют свой идентификатор (свой ID в базе). Класс User без сомнений должен иметь свой ID и свою таблицу в базе.
- Имеют собственный жизненный цикл, ни к чему не привязаны. То есть создаются и удаляются, не зависимо от других сущностей. Когда в системе регистрируется новый пользователь, то появляется строка в таблице, а при выборке новый объект класса User в приложении. И не важно существуют ли какие-то другие записи в таблицах и объекты в приложении.
- На одну и ту же сущность могут ссылаться другие сущности (как в базе, так и в приложении). Если, например, в предметной области есть понятие «статей» с классом Article (и таблицей ARTICLES) и у них есть поле User author, которое ссылается на пользователя, то несколько статей могут ссылаться на один и тот же объект User автора.
Значения (values, на примере класса Name):
- Может не иметь своего идентификатора в базе, может не иметь своей таблицы. Данные из полей класса Name могут храниться в соответствующих полях таблицы USERS.
- Объекты класса Name не имеют собственного жизненного цикла. Когда в приложении создаётся объект класса User для него создаётся объект класса Name для хранения данных об имени пользователя. Когда этот объект класса User удаляется из памяти, то и соответствующий Name встаёт в очередь к сборщику мусора.
- Даже если у пользователей имена совпадают, то для каждого объекта класса User создаётся свой объект класса Name. Не может быть такого, чтобы два разных объекта User ссылались на один и тот же объект Name.
С классом Address можно поступить как угодно. В данном контексте его можно считать и значением, то есть полностью зависимым от User. Можно считать и самостоятельной сущностью. Особенно, если учесть тот факт, что два пользователя могут проживать по одному и тому же адресу. А значит адрес является единой сущностью (а не просто совпадением, как в именах) не только в модели, но и в реальной жизни.
Исходя из этих соображений базу данных можно переделать на две таблицы:
USERS:
ID | FIRSTNAME | MIDDLE_NAME | LASTNAME | ADDRESS_ID | BIRTHDAY | |
1 | Иван | Иванович | Иванов | 1 | 1980-01-01 | ivanov@ivanov.com |
ADDRESSES:
ID | CITY | LOCATION |
1 | Москва | ул. Садовая, д.1, кв.2 |
Сказанное выше вовсе не означает, что у ФИО не может быть отдельной таблицы со своим ID. Но в общем случае, когда модель предметной области отражает что-то более-менее реальное, получается, что количество таблиц меньше, чем количество классов, так как некоторые классы считаются не сущностями, а значениями, и хранят свои данные в таблицах сущностей, от которых они зависят. А собственных таблиц, соответственно, не имеют.
Также важно отметить, что если мы признаём какой-то класс значением, а не сущностью, то мы должны не забыть так или иначе увязать жизненный цикл этого значения с какой-то сущностью, от которой значение будет зависеть. То же касается данных в базе (каскадные удаления и т.п.), особенно если для значения всё-таки делалась отдельная таблица.