Soft Deletion
In the Soft Deletion mode, the remove operation on JPA entities just marks database records as deleted instead of actually deleting them. Later a system administrator can either erase the records completely or restore them.
Soft deletion can help you to reduce the risk of data loss caused by incorrect user actions. It also allows users to make certain records inaccessible instantly even if there are references to them from other tables.
Soft deletion mechanism in Jmix is transparent for application developers. If you define the Soft Delete trait for an entity, the framework marks database records for deleted entity instances, and loads deleted instances according to the following rules:
-
Soft-deleted instances are not returned when loading by Id and are filtered out from results of JPQL queries.
-
In loaded entity graphs, soft-deleted instances are filtered out from collection attributes (To-Many references), but present in single-value attributes (To-One references).
For example, imagine a Customer - Order - OrderLine data model. Initially, an Order referenced a Customer and five instances of OrderLine. You have soft deleted the Customer instance and one of the OrderLine instances. Then if you load the Order together with Customer and collection of OrderLine, it will contain the reference to the deleted Customer and four OrderLine instances in the collection.
Handling of References
When a normal (hard deleted) entity is deleted, foreign keys in the database define handling of references to this entity. By default, you cannot delete an entity if it has references from other entities. To delete the referencing entity together with your entity, or to set the reference to null, you define ON DELETE CASCADE
or ON DELETE SET NULL
rules for the foreign key.
For soft deleted entities foreign keys also exist, but they cannot affect the deletion because there is no deletion from the database standpoint. So by default, when an entity instance is soft deleted, it doesn’t affect any linked entities.
Jmix offers @OnDelete
and @OnDeleteInverse
annotations to handle references between soft-deleted entities.
Studio entity designer contains visual hints to help you choose correct annotations and their values. |
-
@OnDelete
annotation specifies what to do with referenced entity when deleting the current entity. In the following example, allOrderLine
instances are deleted when the owningOrder
instance is deleted:public class Order { // ... @OnDelete(DeletePolicy.CASCADE) @Composition @OneToMany(mappedBy = "order") private List<OrderLine> lines;
-
@OnDeleteInverse
annotation specifies what to do with the current entity when deleting the referenced entity. In the following example, theCustomer
instance cannot be deleted if there is a reference to it from anOrder
instance:public class Order { // ... @OnDeleteInverse(DeletePolicy.DENY) @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CUSTOMER_ID") private Customer customer;
The annotations can have one of the following values:
-
DeletePolicy.DENY
– to throw an exception on attempt to delete an entity if the reference is not null. -
DeletePolicy.CASCADE
– to delete the linked entities together. -
DeletePolicy.UNLINK
– to disconnect the linked entities by setting the reference attribute to null. Use this value only on the owning side of the association (the one with the@JoinColumn
annotation).
Unique Constraints
Soft deletion makes creation of database unique constraints more complicated. A constraint must take into account that there may be multiple records with the same value of the unique field: one non-deleted and any number of soft deleted.
The problem is solved differently for different databases.
Studio generates database migration scripts for unique attributes of soft deleted entities taking into account the database used in your project. |
If you define unique constraints manually without help from Studio, follow the recommendations below.
-
If the database supports partial indexes (PostgreSQL), a unique constraint can be defined like this:
create unique index IDX_CUSTOMER_UNIQ_EMAIL on CUSTOMER (EMAIL) where DELETED_DATE is null
-
If the database permits only one null value in a composite index (Oracle, SQL Server), define a composite index including the
DELETED_DATE
column:create unique index IDX_CUSTOMER_UNIQ_EMAIL on CUSTOMER (EMAIL, DELETED_DATE)
-
Otherwise, you should use an additional column, a trigger to update it, and a composite index for the unique constraint. Here is an example for MySQL:
alter table CUSTOMER add DELETED_DATE_NN datetime not null default '1000-01-01 00:00:00.000'; create unique index IDX_CUSTOMER_UNIQ_EMAIL on CUSTOMER (EMAIL, DELETED_DATE_NN); create trigger CUSTOMER_DELETED_DATE_NN_UPDATE_TRIGGER before update on CUSTOMER for each row begin if not(NEW.DELETED_DATE <=> OLD.DELETED_DATE) then set NEW.DELETED_DATE_NN = if (NEW.DELETED_DATE is null, '1000-01-01 00:00:00.000', NEW.DELETED_DATE); end if; end;
Turning Soft Deletion Off
By default, soft deletion is on for all entities having the Soft Delete trait. But you can turn it off for a particular operation using the PersistenceHints.SOFT_DELETION
hint with the false
value.
-
When loading entities using
DataManager
:@Autowired private DataManager dataManager; public Customer loadHardDeletedCustomer(Id<Customer> customerId) { return dataManager.load(customerId).hint(PersistenceHints.SOFT_DELETION, false).one(); }
Results will include soft deleted instances.
-
When removing entities using
DataManager
:@Autowired private DataManager dataManager; public void hardDeleteCustomer(Customer customer) { dataManager.save( new SaveContext() .removing(customer) .setHint(PersistenceHints.SOFT_DELETION, false) ); }
-
When working with
EntityManager
:@PersistenceContext private EntityManager entityManager; @Transactional public void hardRemoveCustomerByEM(Customer customer) { entityManager.setProperty(PersistenceHints.SOFT_DELETION, false); entityManager.remove(customer); }