У меня есть базовое приложение 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;
...
}
Как выглядит элемент меню? Особенно генерация id?
Какая аннотация есть в ссылке MenuPrice на Menu? @ManyToOne? Можете ли вы показать код для обеих сущностей.
Не добавляйте cascade = CascadeType.ALL в отношения @ManyToOne. Если вы удалите MenuPrice, он попытается удалить его Menu, которое попытается удалить все его MenuPrice. Намного безопаснее применять только минимум CascadeTypes. Я бы также избегал изменения значения FetchType по умолчанию.




Подумай об этом:
@Transactional(readOnly = true)
public class MenuService {
Но мой главный вопрос был бы таков: для чего вы использовали аннотацию @Transactional в приложении весенней загрузки?
@Transactional при удалении метода перезаписывает то, что определено на уровне класса
Мне нужно перечитать руководство, но я думаю, что @Transactional предназначен для использования с EntityManager и не применим к репозиториям. Обновлено: Хорошо, можно смешать
Хорошее объяснение: spring.io/blog/2011/02/10/getting-started-with-spring-data-j pa
Но здесь @Transactional находится на уровне обслуживания, а не на уровне репозитория
@Transactional имеет больше смысла находиться на уровне обслуживания, поскольку здесь вы можете вносить изменения в более чем один репозиторий и хотите, чтобы все изменения были успешными или откатывались вместе.
убедитесь, что в дочерних объектах: 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 для методов, изменяющих базу данных.
Вы определяете двунаправленную связь между 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);
}
Почему вы не удаляете с использованием первичного ключа объекта меню?