Мягкое удаление из БД при определённом условие

Суть вопроса в том, что необходимо удалить категорию только тогда, когда баннеры удалены

Entity в который реализовано мягкое удаление из БД:

    @Entity
    @SQLDelete(sql = "UPDATE Category SET deleted = true WHERE id=?")
    @Where(clause = "deleted=false")
    @Table(name = "Category")
    public class Category extends BaseModelEntity {

    @OneToMany(mappedBy = "category")
    @Getter
    @Setter
    private Set<Banner> banners = new HashSet<>();

    @Getter
    @Setter
    @Column(nullable = false)
    private String name;

    @Getter
    @Setter
    @Column(nullable = false)
    private String req_name;

    @Getter
    @Setter
    private boolean deleted = Boolean.FALSE;

}

Service:

@Service
public class CategoryService {
    @Autowired
    private CategoryRepository categoryRepository;

    public void remove(int id, Category category){
        if(category.getBanners().isEmpty()) {
            categoryRepository.deleteById(id);
        } else {
            System.out.println("не могу удалить");
        }
    }
}

Я использую тест для проверки, что баннер содержит категорию.

   @Test
    public void deleteCategory() {
        Category category = new Category();
        category.setName("test1");
        category.setReq_name("test2");

        Banner banner = new Banner(category);
        banner.setName("first");
        banner.setPrice(8.2);
        banner.setText("");


        Banner banner1 = new Banner(category);
        banner1.setName("second");
        banner1.setPrice(8.2);
        banner1.setText("");


        categoryRepository.save(category);
        bannerRepository.save(banner1);

        Assertions.assertFalse(category.getBanners().isEmpty());
    }
}

Получаю ошибку, что категория пуста, хотя я заинжектил категория через конструктор баннера.

org.opentest4j.AssertionFailedError: 
Expected :false
Actual   :true

Что не так в данном условие удаления? При тестирования метода remove категория удаляется, если она содержит баннеры.


Ответы (1 шт):

Автор решения: tequillaz

Получаю ошибку, что категория пуста, хотя я заинжектил категория через конструктор баннера.

В категории нет баннеров потому, что Hibernate не обновляет ее автоматически после сохранения баннера. Чтобы баннер появился в категории после сохранения, категорию необходимо перезапросить. Тест может выглядеть как-то так:

    @Test
    public void deleteCategory() {
        Category category = new Category();
        category.setName("test1");
        category.setReq_name("test2");

        Banner banner = new Banner(category);
        banner.setName("first");
        banner.setPrice(8.2);
        banner.setText("");


        Banner banner1 = new Banner(category);
        banner1.setName("second");
        banner1.setPrice(8.2);
        banner1.setText("");


        category = categoryRepository.save(category);
        bannerRepository.save(banner1);
        category = categoryRepository.findById(category.getId())

        Assertions.assertFalse(category.getBanners().isEmpty());
    }

Не забывайте, что Hibernate для каждой транзакции создает собственный транзакционный кеш. Тест будет работать до тех пор, пока каждый запрос в БД выполняется в отдельной транзакции, т.е. не будет кеширования данных. Как только метод deleteCategory будет помечен с помощью @Transactional, запросы из репозиториев будут выполняться с использованием транзакционного кеша, а в нем создаваемая в методе категория не будет содержать ни одного баннера, т.к. Hibernate не обновляет связанные данные в кеше автоматически. Что бы связи между сущностями после их сохранения появились, необходимо разделять операции сохранения и запроса по разным транзакциям в тесте.

Что бы избежать сложностей с сохранением/перезапросом сущностей, можно сделать каскадное сохранение сущностей. В таком случае, при сохранении категория уже будет содержать баннеры:

@Entity
@SQLDelete(sql = "UPDATE Category SET deleted = true WHERE id=?")
@Where(clause = "deleted=false")
@Table(name = "Category")
public class Category extends BaseModelEntity {

    @OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
    @Getter
    @Setter
    private Set<Banner> banners = new HashSet<>();

    @Getter
    @Setter
    @Column(nullable = false)
    private String name;

    @Getter
    @Setter
    @Column(nullable = false)
    private String req_name;

    @Getter
    @Setter
    private boolean deleted = Boolean.FALSE;

}

Тест упростится до

    @Test
    public void deleteCategory() {
        Category category = new Category();
        category.setName("test1");
        category.setReq_name("test2");

        Banner banner = new Banner(category); // Ссылка на категорию
        banner.setName("first");
        banner.setPrice(8.2);
        banner.setText("");

        Banner banner1 = new Banner(category); // Ссылка на категорию
        banner1.setName("second");
        banner1.setPrice(8.2);
        banner1.setText("");

        // Добавление баннеров в категорию для каскадного сохранения
        category.setBanners(Sets.newHashSet(banner, banner1));
        category = categoryRepository.save(category);

        Assertions.assertFalse(category.getBanners().isEmpty());
    }
→ Ссылка