en Peris
en Peris

Reputation: 1717

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

Upvotes: 5

Views: 5958

Answers (5)

George Z.
George Z.

Reputation: 6808

For a simpler thing that worked is to add @PreRemove to the parent that sets the child value to null: My concept was an @OneToOne relationship where a Registration object has a Subscription object. They both had foreign key to each other.

Inside Registration class:

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

Upvotes: 0

Jean Marois
Jean Marois

Reputation: 1600

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.

Upvotes: 3

ner0
ner0

Reputation: 105

Try removing the @Transactional(readOnly = true) from MenuService. This could prevent the flushing.

Otherwise I would try the approach of en Lopes. There are diffrent CascadeType: https://docs.oracle.com/javaee/6/api/javax/persistence/CascadeType.html

Upvotes: 0

Dennis G.
Dennis G.

Reputation: 26

Think about that:

@Transactional(readOnly = true)
public class MenuService {

But my main question would be: For what did you use the @Transactional annotation within a spring boot app?

Upvotes: 0

en Lopes
en Lopes

Reputation: 2113

make sure that in the child objects: MenuPrice, MenuPriceSummary you have CascadeType.ALL , something like

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

Upvotes: 1

Related Questions