CrudRepository не удаляет объект со связью

У меня есть базовое приложение SpringBoot. с использованием Spring Initializer, JPA, встроенного Tomcat, механизма шаблонов Thymeleaf и пакета в виде исполняемого файла JAR. Я создал этот класс репозитория:

@Repository
public interface MenuRepository extends CrudRepository<Menu, Long> {
..
}

и этот класс обслуживания

@Service
@Transactional(readOnly = true)
public class MenuService {

     @Autowired
     protected MenuRepository menuRepository;

     @Transactional
     public void delete (Menu menu) {
         menuRepository.delete  (menu);
     }
     ..
}

и этот тест Junit:

@ContextConfiguration(classes = {TestSystemConfig.class})
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MenuGestApplication.class) 
public class MenuServiceTests {
...
@Test
    public void testDelete () {

        Menu menu = new menu(); 
        menu.setmenuId("bacalla-amb-tomaquet");
        menuService.save(menu);

        MenuPrice menuPrice = new menuPrice(menu);
        menuPrice.setPrice((float)20.0);
        menuPriceService.save(menuPrice);

        MenuPriceSummary menuPriceSummary = new menuPriceSummary(menu);
        menuPriceSummary.setFortnightlyAvgPrice((float)20.0);

        menuPriceSummaryService.save(menuPriceSummary);

        menu = menuService.findBymenuId("bacalla-amb-tomaquet");

        assertNotNull (menu);

        menuService.delete (menu);

        menu = menuService.findBymenuId("bacalla-amb-tomaquet");

        assertNull (menu);

    }
}

Но Junit не работает, потому что объект не удаляется и исключение не генерируется!

У меня это в проертах, как и предлагали ..

@OneToMany(mappedBy = "menu", cascade = CascadeType.ALL,  orphanRemoval = true, fetch=FetchType.LAZY)
    private List<MenuPrice> price;

даже то, что я вижу это в консоли при запуске тестов:

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`elcormenu`.`t_menu_price`, CONSTRAINT `FK19d0sljpshu4g8wfhrkqj7j7w` FOREIGN KEY (`menu_id`) REFERENCES `t_menu` (`id`))

и класс Menu:

@Entity
@Table(name = "t_menu")
public class Menu  implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

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

    @JsonProperty("MenuId")
    private String MenuId;

    @OneToMany(mappedBy = "Menu", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @JsonIgnore
    private Set<MenuPrice> MenuPrice = new HashSet<>();

    @OneToOne(mappedBy = "Menu", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JsonIgnore
    private MenuPriceSummary summary;
...
}

Почему вы не удаляете с использованием первичного ключа объекта меню?

Sudhir Ojha 12.06.2018 15:19

Как выглядит элемент меню? Особенно генерация id?

C. Weber 18.06.2018 15:35

Какая аннотация есть в ссылке MenuPrice на Menu? @ManyToOne? Можете ли вы показать код для обеих сущностей.

Jean Marois 19.06.2018 07:27

Не добавляйте cascade = CascadeType.ALL в отношения @ManyToOne. Если вы удалите MenuPrice, он попытается удалить его Menu, которое попытается удалить все его MenuPrice. Намного безопаснее применять только минимум CascadeTypes. Я бы также избегал изменения значения FetchType по умолчанию.

Jean Marois 19.06.2018 07:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
4
4 194
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Подумай об этом:

@Transactional(readOnly = true)
public class MenuService {

Но мой главный вопрос был бы таков: для чего вы использовали аннотацию @Transactional в приложении весенней загрузки?

@Transactional при удалении метода перезаписывает то, что определено на уровне класса

en Peris 12.06.2018 15:16

Мне нужно перечитать руководство, но я думаю, что @Transactional предназначен для использования с EntityManager и не применим к репозиториям. Обновлено: Хорошо, можно смешать

Dennis G. 12.06.2018 15:38

Хорошее объяснение: spring.io/blog/2011/02/10/getting-started-with-spring-data-j‌ pa

Dennis G. 12.06.2018 15:45

Но здесь @Transactional находится на уровне обслуживания, а не на уровне репозитория

en Peris 12.06.2018 16:22

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

Jean Marois 19.06.2018 07:33

убедитесь, что в дочерних объектах: MenuPrice, MenuPriceSummary у вас есть CascadeType.ALL, что-то вроде

@OneToMany(mappedBy = "menu", cascade = CascadeType.ALL, fetch=FetchType.LAZY)
private List<MenuPrice> price;

Попробуйте удалить @Transactional (readOnly = true) из MenuService. Это может помешать промыванию.

В противном случае я бы попробовал подход en Lopes. Существуют разные CascadeType: https://docs.oracle.com/javaee/6/api/javax/persistence/CascadeType.html

То, как он использует @Transaction, является наилучшей практикой; @Transactional(readOnly=true) для класса и @Transactional для методов, изменяющих базу данных.

Jean Marois 19.06.2018 07:31
Ответ принят как подходящий

Вы определяете двунаправленную связь между Menu и MenuPrice, используя как @OneToMany, так и @ManyToOne. Когда у вас двунаправленные отношения, вам нужно установить обе стороны. В тесте вы устанавливаете Menu в MenuPrice, но не добавляете MenuPrice в MenuPrice Set. Включите следующий оператор menu.MenuPrice.add(menuPrice); после создания menuPrice. Вам также не нужно несколько save(), поскольку вы указали Cascade.All. Попробуйте следующее:

public void testDelete () {

    Menu menu = new menu(); 
    menu.setmenuId("bacalla-amb-tomaquet");

    MenuPrice menuPrice = new menuPrice(menu);
    menuPrice.setPrice((float)20.0);

    // Not needed
    // menuPriceService.save(menuPrice);

    // Add menuPrice to menu's menuPrice set
    menu.menuPrice.add(menuPrice);

    MenuPriceSummary menuPriceSummary = new menuPriceSummary(menu);
    menuPriceSummary.setFortnightlyAvgPrice((float)20.0);

    // Set menuPriceSummary in menu        
    menu.summary = menuPriceSummary;

    // Not needed
    //menuPriceSummaryService.save(menuPriceSummary);

    // Saving menu will save it children too
    menuService.save(menu);

    menu = menuService.findBymenuId("bacalla-amb-tomaquet");

    assertNotNull (menu);

    menuService.delete (menu);

    menu = menuService.findBymenuId("bacalla-amb-tomaquet");

    assertNull (menu);

}

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

Более простая вещь, которая сработала, - это добавить @PreRemove к родительскому элементу, который устанавливает дочернее значение на null: Моя концепция была связью @OneToOne, где объект Registration имеет объект Subscription. У них обоих был внешний ключ друг к другу.

Внутри класса Registration:

@PreRemove
private void preRemove() {
    setSubscription(null);
}

Другие вопросы по теме