4.2.1.4.2. Политика обработки связей

Платформа предоставляет средство обработки связей при удалении сущностей, во многом аналогичное правилам ON DELETE внешних ключей в базе данных. Это средство работает на уровне Middleware и использует аннотации @OnDelete, @OnDeleteInverse атрибутов сущности.

Аннотация @OnDelete обрабатывается при удалении той сущности, в которой она встретилась, а не той, на которую указывает аннотированный атрибут (в этом отличие от каскадных удалений на уровне БД).

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

Значением аннотации может быть:

  • DeletePolicy.DENY - запретить удаление сущности, если аннотированный атрибут не null или не пустая коллекция

  • DeletePolicy.CASCADE - каскадно удалить аннотированный атрибут

  • DeletePolicy.UNLINK - разорвать связь с аннотированным атрибутом. Разрыв связи имеет смысл указывать только на ведущей стороне ассоциации - той, которая в классе сущности аннотирована @JoinColumn.

Примеры:

  1. Запрет удаления при наличии ссылки: при попытке удаления экземпляра Customer, на который ссылается хотя бы один Order, будет выброшено исключение DeletePolicyException.

    Order.java

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CUSTOMER_ID")
    @OnDeleteInverse(DeletePolicy.DENY)
    protected Customer customer;

    Customer.java

    @OneToMany(mappedBy = "customer")
    protected List<Order> orders;
  2. Каскадное удаление элементов коллекции: при удалении экземпляра Role все экземпляры Permission также будут удалены.

    Role.java

    @OneToMany(mappedBy = "role")
    @OnDelete(DeletePolicy.CASCADE)
    protected Set<Permission> permissions;

    Permission.java

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ROLE_ID")
    protected Role role;
  3. Разрыв связи с элементами коллекции: удаление экземпляра Role приведет к установке в null ссылок со стороны всех входивших в коллекцию экземпляров Permission.

    Role.java

    @OneToMany(mappedBy = "role")
    protected Set<Permission> permissions;

    Permission.java

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ROLE_ID")
    @OnDeleteInverse(DeletePolicy.UNLINK)
    protected Role role;

Особенности реализации:

  1. Нужно быть осторожным при использовании @OnDeleteInverse с политиками CASCADE и UNLINK, так как при этом происходит извлечение из БД на сервер приложения всех экземпляров ссылающихся объектов, изменение и затем сохранение.

    Например, в случае ассоциации Customer - Job и большого количества работ для одного заказчика, если поставить на атрибут Job.customer политику @OnDeleteInverse(CASCADE), то при удалении экземпляра заказчика будет предпринята попытка извлечь и изменить все его работы. Это может привести к перегрузке сервера приложения и БД.

    С другой стороны, использование @OnDeleteInverse(DENY) безопасно, так как при этом производится только подсчет количества ссылающихся объектов, и если оно больше 0, выбрасывается исключение. Поэтому @OnDeleteInverse(DENY) для атрибута Job.customer вполне допустимо.

  2. Политика обработки связей реализуется с помощью Entity Listeners, то есть при сохранении данных в БД на уровне Middleware.