Перейти к основному содержимому

Как реализовать мягкое удаление с помощью Spring JPA

· 6 мин. чтения

1. Введение

Физическое удаление данных из таблицы является обычным требованием при взаимодействии с базами данных. Но иногда есть бизнес-требования не удалять данные из базы данных без возможности восстановления. Эти требования, например, необходимость отслеживания или аудита истории данных, а также связанные с целостностью ссылок.

Вместо физического удаления данных мы можем просто скрыть эти данные, чтобы к ним нельзя было получить доступ из внешнего интерфейса приложения.

В этом руководстве мы узнаем о мягком удалении и о том, как реализовать эту технику с помощью Spring JPA .

2. Что такое мягкое удаление?

Обратимое удаление выполняет процесс обновления, чтобы пометить некоторые данные как удаленные, вместо физического удаления их из таблицы в базе данных. Обычный способ реализовать обратимое удаление — добавить поле, которое будет указывать, были ли данные удалены или нет.

Например, предположим, что у нас есть таблица продуктов со следующей структурой:

./10bb5a50a7f863ebe9ccd5f056ec9557.png

Давайте теперь посмотрим на команду SQL, которую мы будем запускать при физическом удалении записи из таблицы:

delete from table_product where id=1

Эта команда SQL безвозвратно удалит продукт с id=1 из таблицы в базе данных.

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

./a163768bc93648754067c40c399a03e2.png

Обратите внимание, что мы добавили новое поле под названием « удалено». Это поле будет содержать значения 0 или 1 .

Значение 1 будет означать, что данные были удалены, а 0 — что данные не были удалены. Мы должны установить 0 в качестве значения по умолчанию, и для каждого процесса удаления данных мы не запускаем команду удаления SQL, а вместо этого запускаем следующую команду обновления SQL:

update from table_product set deleted=1 where id=1

Используя эту команду SQL, мы фактически не удаляли строку, а только помечали ее как удаленную. Итак, когда мы собираемся выполнить запрос на чтение и нам нужны только те строки, которые не были удалены, мы должны добавить фильтр только в наш SQL-запрос:

select * from table_product where deleted=0

3. Как реализовать мягкое удаление в Spring JPA

С Spring JPA реализация обратимого удаления стала намного проще. Для этой цели нам понадобится всего несколько аннотаций JPA.

Как мы знаем, мы обычно используем только несколько команд SQL с JPA. Он будет создавать и выполнять большинство запросов SQL за кулисами.

Давайте теперь реализуем обратимое удаление в Spring JPA с тем же примером таблицы, что и выше.

3.1. Класс сущности

Наиболее важной частью является создание класса сущности.

Давайте создадим класс сущности Product :

@Entity
@Table(name = "table_product")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private double price;

private boolean deleted = Boolean.FALSE;

// setter getter methods
}

Как мы видим, мы добавили удаленное свойство со значением по умолчанию FALSE .

Следующим шагом будет переопределение команды удаления в репозитории JPA.

По умолчанию команда удаления в репозитории JPA запускает запрос на удаление SQL, поэтому давайте сначала добавим некоторые аннотации к нашему классу сущностей:

@Entity
@Table(name = "table_product")
@SQLDelete(sql = "UPDATE table_product SET deleted = true WHERE id=?")
@Where(clause = "deleted=false")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private double price;

private boolean deleted = Boolean.FALSE;

// setter getter method
}

Мы используем аннотацию @SQLDelete для переопределения команды удаления. Каждый раз, когда мы выполняем команду удаления, мы фактически превращаем ее в команду обновления SQL, которая изменяет значение удаленного поля на true , а не удаляет данные навсегда.

С другой стороны, аннотация @Where добавит фильтр при чтении данных о продукте. Таким образом, согласно приведенному выше примеру кода, данные о продукте со значением удалено = true не будут включены в результаты.

3.2. Репозиторий

Особых изменений в классе репозитория нет, мы можем написать его как обычный класс репозитория в приложении Spring Boot:

public interface ProductRepository extends CrudRepository<Product, Long>{

}

3.3. обслуживание

Также по сервисному классу пока ничего особенного нет. Мы можем вызывать функции из репозитория, который нам нужен.

В этом примере давайте вызовем три функции репозитория для создания записи, а затем выполним обратимое удаление:

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;

public Product create(Product product) {
return productRepository.save(product);
}

public void remove(Long id){
productRepository.deleteById(id);
}

public Iterable<Product> findAll(){
return productRepository.findAll();
}
}

4. Как получить удаленные данные?

Используя аннотацию @Where , мы не можем получить удаленные данные о продукте, если мы все еще хотим, чтобы удаленные данные были доступны. Примером этого является пользователь с уровнем администратора, который имеет полный доступ и может просматривать данные, которые были «удалены».

Чтобы реализовать это, мы должны использовать не аннотацию @Where , а две разные аннотации: @FilterDef и @Filter . С помощью этих аннотаций мы можем динамически добавлять условия по мере необходимости:

@Entity
@Table(name = "tbl_products")
@SQLDelete(sql = "UPDATE tbl_products SET deleted = true WHERE id=?")
<strong>@FilterDef(name = "deletedProductFilter", parameters = @ParamDef(name = "isDeleted", type = "boolean"))
@Filter(name = "deletedProductFilter", condition = "deleted = :isDeleted")</strong>
public class Product {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

private double price;

private boolean deleted = Boolean.FALSE;
}

Здесь аннотация @FilterDef определяет основные требования, которые будут использоваться аннотацией @Filter . Кроме того, нам также нужно изменить функцию findAll() в сервисном классе ProductService для обработки динамических параметров или фильтров:

@Service
public class ProductService {

@Autowired
private ProductRepository productRepository;

<strong> @Autowired
private EntityManager entityManager;</strong>

public Product create(Product product) {
return productRepository.save(product);
}

public void remove(Long id){
productRepository.deleteById(id);
}

public Iterable<Product> findAll(<strong>boolean isDeleted</strong>){
<strong> Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("deletedProductFilter");
filter.setParameter("isDeleted", isDeleted);
Iterable<Product> products = productRepository.findAll();
session.disableFilter("deletedProductFilter");
return products;</strong>
}
}

Здесь мы добавляем параметр isDeleted , который мы добавим в объект Filter , влияющий на процесс чтения сущности Product .

5. Вывод

Методы мягкого удаления легко реализовать с помощью Spring JPA. Что нам нужно сделать, так это определить поле, в котором будет храниться информация о том, была ли строка удалена или нет. Затем мы должны переопределить команду удаления, используя аннотацию @SQLDelete для этого конкретного класса сущностей.

Если нам нужен больший контроль, мы можем использовать аннотации @FilterDef и @Filter , чтобы мы могли определить, должны ли результаты запроса включать удаленные данные или нет.

Весь код в этой статье доступен на GitHub .