1. Введение
Физическое удаление данных из таблицы является обычным требованием при взаимодействии с базами данных. Но иногда есть бизнес-требования не удалять данные из базы данных без возможности восстановления. Эти требования, например, необходимость отслеживания или аудита истории данных, а также связанные с целостностью ссылок.
Вместо физического удаления данных мы можем просто скрыть эти данные, чтобы к ним нельзя было получить доступ из внешнего интерфейса приложения.
В этом руководстве мы узнаем о мягком удалении и о том, как реализовать эту технику с помощью Spring JPA .
2. Что такое мягкое удаление?
Обратимое удаление выполняет процесс обновления, чтобы пометить некоторые данные как удаленные, вместо физического удаления их из таблицы в базе данных. Обычный способ реализовать обратимое удаление — добавить поле, которое будет указывать, были ли данные удалены или нет.
Например, предположим, что у нас есть таблица продуктов со следующей структурой:
Давайте теперь посмотрим на команду SQL, которую мы будем запускать при физическом удалении записи из таблицы:
delete from table_product where id=1
Эта команда SQL безвозвратно удалит продукт с id=1
из таблицы в базе данных.
Давайте теперь реализуем механизм мягкого удаления, описанный выше:
Обратите внимание, что мы добавили новое поле под названием « удалено».
Это поле будет содержать значения 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 .