Search code examples
javaspring-mvcspring-bootjunitjunit4

CrudRepository does not delete an Object with relationship


I have a basic SpringBoot app. using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file. I have created this Repository class:

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

and this service class

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

     @Autowired
     protected MenuRepository menuRepository;

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

and this Junit Test:

@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);

    }
}

But the Junit is failing because the object is not deleted and no exception is thrown !

I have this in the proerty, as suggested..

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

even that I see this in the console when running the tests:

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`))

and the Menu class:

@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;
...
}

Solution

  • You are defining a Bidirectional relationship between Menu and MenuPrice by using both @OneToMany and @ManyToOne. When you have a biodirectional relationship you need to set both sides. In the test you are setting Menu in MenuPrice but not adding the MenuPrice to Menu's MenuPrice Set. Include the following statement menu.MenuPrice.add(menuPrice); after you create menuPrice. You also do not need multiple save() since you have specified Cascade.All. Try the following:

    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);
    
    }
    

    In may cases, you don't need bidirectional relationships and can remove the @OneToMany, but this depends on how your business logic needs to navigate to the children or query it via JPA.