CrudRepository 不会删除有关系的对象

Posted

技术标签:

【中文标题】CrudRepository 不会删除有关系的对象【英文标题】:CrudRepository does not delete an Object with relationship 【发布时间】:2018-11-21 21:45:48 【问题描述】:

我有一个基本的 SpringBoot 应用程序。使用 Spring Initializer、JPA、嵌入式 Tomcat、Thymeleaf 模板引擎,并将其打包为可执行的 JAR 文件。 我已经创建了这个 Repository 类:

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

和菜单类:

@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?你能显示两个实体的代码吗? 不要在@ManyToOne 关系上添加cascade = CascadeType.ALL。如果您确实删除了 MenuPrice 将尝试删除它的 Menu,这将尝试删除它的所有 MenuPrices。仅应用最低限度的 CascadeType 会更安全。我也会避免修改默认的 FetchType。 【参考方案1】:

您正在使用@OneToMany@ManyToOne 定义Menu 和MenuPrice 之间的双向关系。当您有双向关系时,您需要设置双方。在测试中,您在 MenuPrice 中设置 Menu,但未将 MenuPrice 添加到 Menu 的 MenuPrice Set 中。在创建 menuPrice 后包括以下语句 menu.MenuPrice.add(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 查询。

【讨论】:

【参考方案2】:

确保在子对象中:MenuPrice、MenuPriceSummary 你有 CascadeType.ALL 之类的东西

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

【讨论】:

【参考方案3】:

想一想:

@Transactional(readOnly = true)
public class MenuService 

但我的主要问题是:您在 Spring Boot 应用程序中使用 @Transactional 注释做什么?

【讨论】:

@Transactional on delete 方法覆盖类级别中定义的内容 我必须重新阅读手册,但我认为@Transactional 与 EntityManager 一起使用,不适用于存储库。编辑:好的,可以混合 好解释:spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa 但是这里@Transactional 是在服务级别,而不是存储库级别 @Transactional 处于服务级别更有意义,因为您可能会对多个存储库进行更改,并希望所有更改成功或一起回滚。【参考方案4】:

尝试从MenuService 中删除@Transactional(readOnly = true)。这可以防止冲洗。

否则我会尝试 en Lopes 的方法。有不同的CascadeType: https://docs.oracle.com/javaee/6/api/javax/persistence/CascadeType.html

【讨论】:

他使用@Transaction的方式是最佳实践; @Transactional(readOnly=true) 用于类,@Transactional 用于修改数据库的方法。【参考方案5】:

更简单的方法是将@PreRemove 添加到将子值设置为null 的父级:我的概念是@OneToOne 关系,其中Registration 对象具有Subscription 对象。他们都有彼此的外键。

Registration类里面:

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

【讨论】:

以上是关于CrudRepository 不会删除有关系的对象的主要内容,如果未能解决你的问题,请参考以下文章

CoreData 获取请求 - 获取所有有关系的对象

使用Backand BaaS时如何创建有关系的对象?

当我尝试删除与其他表有关系的任何公司时,我正在使用 PostgreSQL 和 GraphQL。它显示 GraphQL 错误:

在Doctrine中插入有关系的记录后获取ID

面向对象02

计算在 Laravel 中有关系的关系