Связанные объекты OneToOne

Вводная

В этой статье я хотел бы описать тип связи Один к Одному, которую предоставляет нам JPA (Java Persistence API) и работу с ним. Казалось бы, что тут ещё обсуждать? На просторах интернета и так полно примеров реализаций на любой вкус. Совершенно верно, потому не станем повторять, того, что уже сказано. Здесь, я бы хотел рассмотреть подводные камни этой реализации и копнуть так сказать вглубь.

Итак, для того, чтобы связать две сущности в базе данных, существует JPA аннотация @OneToOne выглядит примерно так:


@OneToOne
private User user;

В этом случае, некоторому объекту соответствует объект User, который представлен в базе данных как связь один к одному. При этом в таблице некоторого объекта будет создано поле user_id (Предполагается, что первичным ключом выступает поле id в объекте User), в котором будет сохраняться идентификатор ссылающейся записи. Стоит отметить, что в текущем варианте, поле user, может быть равен null.

В случае, если мы хотим предотвратить сохранение некоторого объекта без ссылка на объект User, следует указать аннотацию @NotNull, которая в свою очередь изменит тип записи в базе данных и пометит его как NOT NULL, а так же озадачит JPA проверить существование объекта User. Если же поле user будет ссылаться в null, будет выкинуто исключение ConstraintViolationException, которое известит о том, что поле user не может быть пустым. Так, мы обязаны для начала создать или получить объект User, а потом присвоить его полю user.

Иногда, возникает ситуация, когда объект должен сохранить вложенных объект каскадно. Рассмотрим такую ситуацию: Некоторый объект, имеет поле тег, которое хранит в себе категорию или тег некоторой записи и при этом, пользователям дана возможность создавать произвольные теги самостоятельно. Посмотрим, как это выглядит


@OneToOne(cascade = CascadeType.PERSIST)
private Tag tag;

Тут у некоторого объекта определено поле tag, которое может быть указано пользователями системы. Так же указали свойство cascade, в котором регламентируем поведение некоторого объекта сохранять предложенный тег. Важно!!! Тег при этом должен быть в состоянии transient (Поле id не должно быть указано) в противном случае, мы получим исключение PersistenceException. Это связано со стратегией, в которой регламентируется исключительно сохранение нового объекта в базе данных. Если же мы хотим предложить пользователям выбор из существующего набора тегов, то наша запись должна выглядеть так:


@OneToOne(cascade = CascadeType.MERGE)
private Tag tag;

По этой стратегии, JPA не будет создавать новые записи в БД, но будет использовать существующие. Если же поля id в поле tag не будет указано, т.е. состояние объекта равно transient, то мы получаем исключение IllegalStateException, так как указанная стратегия не предусматривает сохранения новых записей в БД.

Если же мы хотим предусмотреть и тот и другой вариант, то наша запись должна выглядеть так:


@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Tag tag;

Здесь, мы указываем стратегию создания новых записей или использование существующих в зависимости от того, в каком состоянии объект tag нам был передан, либо это transient (новый объект) либо detached/persistent (существующий). Однако, в Spring Data существует один нюанс, сохранение detached/persistent объекта не приведёт к ожидаемому результату, вместо этого, мы получим исключение PersistenceException. Причиной тому, является сохранение объекта вне транзакции. Для того, чтобы наш код заработал ожидаемым образом, следует обернуть его в транзакцию, допустим в сервисе.

Бывает ситуация, когда мы должны предложить пользователю удалить тег из системы (в том случае, если наша система предполагает для каждого пользователя свой тег). Задача состоит в том, что мы должны при обновлении существующего объекта удалить тег. Давайте рассмотрим эту ситуацию


@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
private Tag tag;

Как вы можете видеть, здесь мы даём возможность пользователю создавать, использовать и удалять тег. Давайте внимательно посмотрим на представленный код. Как вы можете видеть, мы не используем CascadeType.REMOVE. Вы спросите почему? На самом деле я бы тоже хотел это знать ) Я пока не разобрался в этом, потому опущу этот момент.

Остальные свойства рассмотрим уже в следующих статьях или в обновлении существующей.

jpa
© JavaSE.ru