Spring数据保存不会删除

时间:2017-10-02 15:33:26

标签: spring hibernate spring-data-jpa

Cookbook中的

removeRecipe会从Recipe中删除Cookbook。将引用设置为null并从集合中删除实体后,不会删除实体。

@Entity
public class Cookbook implements Identifiable<Cookbook> {
    private static final Logger LOG = LoggerFactory.getLogger(Cookbook.class);
    private Long id;
    private String title;
    private List<CookbookRecipe> cookbookRecipes = new ArrayList<>();

    public Cookbook() {}

    public Cookbook(String title) {
        this.title = title;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Basic
    @Column(name = "title")
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "cookbook", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, orphanRemoval = true)
    public List<CookbookRecipe> getCookbookRecipes() {
        return cookbookRecipes;
    }

    /**
     * The setter is called by hibernate.
     * @param cookbookRecipes maybe null, maybe the collection is not even ready for read access.
     *                    Don't do anything with the collection here!
     */
    public void setCookbookRecipes(List<CookbookRecipe> cookbookRecipes) {
        this.cookbookRecipes = cookbookRecipes;
    }

    /**
     * Returns a List that must remain unchanged.
     */
    @Transient
    public List<Recipe> getRecipes() {
        return Collections.unmodifiableList(getCookbookRecipes().stream().map(CookbookRecipe::getRecipe).collect(Collectors.toList()));
    }

    public void addRecipe(Recipe recipe, String createdBy, Date createdDate) {
        final CookbookRecipe cookbookRecipe = new CookbookRecipe(this, recipe);
        cookbookRecipe.setCreatedBy(createdBy);
        cookbookRecipe.setCreatedDate(createdDate);
        if( !cookbookRecipes.contains(cookbookRecipe) && !recipe.getCookbookRecipes().contains(cookbookRecipe)) {
            if( !cookbookRecipes.add(cookbookRecipe) ) {
                LOG.error("Failed to add cookbookRecipe " + cookbookRecipe + " to collection cookbookRecipes " + cookbookRecipes);
            }
            if( !recipe.getCookbookRecipes().add( cookbookRecipe ) ) {
                LOG.error("Failed to add cookbookRecipe " + cookbookRecipe + " to collection recipe.getCookbookRecipes " + recipe.getCookbookRecipes());
            }
        }
    }

    public void removeRecipe(Recipe recipe) {

        for (Iterator<CookbookRecipe> iterator = cookbookRecipes.iterator();
             iterator.hasNext(); ) {
            CookbookRecipe cookbookRecipe = iterator.next();

            if (cookbookRecipe.getCookbook().equals(this) &&
                    cookbookRecipe.getRecipe().equals(recipe)) {
                iterator.remove();
                recipe.getCookbookRecipes().remove(cookbookRecipe);
                cookbookRecipe.setCookbook(null);
                cookbookRecipe.setRecipe(null);
            }
        }
    }

    @Override
    public String toString() {
        return title;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Cookbook that = (Cookbook) o;
        return getId() != null && Objects.equals(getId(), that.getId());
    }

    @Override
    public int hashCode() {
        return 31;
    }

    @Override
    public boolean equalsByBusinessKey(Cookbook other) {
        if (this == other) return true;
        if (other == null || getClass() != other.getClass()) return false;
        return Objects.equals(getTitle(), other.getTitle());
    }
}

@Entity
@Table(name = "cookbook_recipe")
public class CookbookRecipe implements Serializable {
    @EmbeddedId
    private CookbookRecipePk pk;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @MapsId("cookbookId")
    private Cookbook cookbook;

    @ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @MapsId("recipeId")
    private Recipe recipe;

    private Date createdDate;
    private String createdBy;

    public CookbookRecipe() {
    }

    public CookbookRecipe(Cookbook cookbook, Recipe recipe) {
        this.cookbook = cookbook;
        this.recipe = recipe;
        this.pk = new CookbookRecipePk(cookbook.getId(), recipe.getId());
    }

    public CookbookRecipePk getPk() {
        return pk;
    }

    public void setPk(CookbookRecipePk pk) {
        this.pk = pk;
    }

    @Transient
    public Cookbook getCookbook() {
        return cookbook;
    }

    public void setCookbook(Cookbook cookbook) {
        this.cookbook = cookbook;
    }

    @Transient
    public Recipe getRecipe() {
        return recipe;
    }

    public void setRecipe(Recipe recipe) {
        this.recipe = recipe;
    }

    @Temporal(TemporalType.DATE)
    @Column(name = "CREATED_DATE", nullable = false, length = 10)
    public Date getCreatedDate() {
        return this.createdDate;
    }

    public void setCreatedDate(Date createdDate) {
        this.createdDate = createdDate;
    }

    @Column(name = "CREATED_BY", nullable = false, length = 10)
    public String getCreatedBy() {
        return this.createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        CookbookRecipe that = (CookbookRecipe) o;

        return Objects.equals(getPk(), that.getPk());
    }

    public int hashCode() {
        return 31;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("CookbookRecipe{");
        sb.append("pk=")
          .append(pk);
        sb.append('}');
        return sb.toString();
    }
}

@Embeddable
public class CookbookRecipePk implements java.io.Serializable {

    @Column(name = "cookbook_id")
    private Long cookbookId;

    @Column(name = "recipe_id")
    private Long recipeId;

    public CookbookRecipePk() {}

    public CookbookRecipePk(Long cookbookId, Long recipeId) {
        this.cookbookId = cookbookId;
        this.recipeId = recipeId;
    }

    public Long getCookbookId() {
        return cookbookId;
    }

    public void setCookbookId(Long cookbookId) {
        this.cookbookId = cookbookId;
    }

    public Long getRecipeId() {
        return recipeId;
    }

    public void setRecipeId(Long recipeId) {
        this.recipeId = recipeId;
    }


    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        CookbookRecipePk that = (CookbookRecipePk) o;

        return null != cookbookId && null != recipeId &&
                Objects.equals(cookbookId, that.cookbookId) &&
                Objects.equals(recipeId, that.recipeId);
    }

    public int hashCode() {
        return 31;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("CookbookRecipePk{");
        sb.append("cookbookId=")
          .append(cookbookId);
        sb.append(", recipeId=")
          .append(recipeId);
        sb.append('}');
        return sb.toString();
    }
}

@Entity
public class Recipe implements Serializable, Identifiable<Recipe> {
    private Long id;
    private String title;
    private Category category;
    private List<CookbookRecipe> cookbookRecipes = new ArrayList<>();

    public Recipe(String title) {
        this.title = title;
    }

    public Recipe() {}

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Basic
    @Column(name = "title")
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "category_id", referencedColumnName = "id")
    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "recipe", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
    public List<CookbookRecipe> getCookbookRecipes() {
        return cookbookRecipes;
    }

    public void setCookbookRecipes(List<CookbookRecipe> cookbookRecipes) {
        this.cookbookRecipes = cookbookRecipes;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Recipe that = (Recipe) o;
        return getId() != null && Objects.equals(getId(), that.getId());
    }

    @Override
    public int hashCode() {
        return 31;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Recipe{");
        sb.append("id=")
          .append(id);
        sb.append(", title='")
          .append(title)
          .append('\'');
        sb.append(", cookbookRecipes=")
          .append(cookbookRecipes);
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equalsByBusinessKey(Recipe other) {
        if (this == other) return true;
        if (other == null || getClass() != other.getClass()) return false;
        return Objects.equals(getTitle(), other.getTitle());
    }
}

测试

@RunWith(SpringRunner.class)
@DataJpaTest
public class CookbookRepositoryIntegrationTest {

    @Autowired
    RecipeRepository recipeRepository;
    @Autowired
    CookbookRepository cookbookRepository;
    @Autowired
    CookbookRecipeRepository cookbookRecipeRepository;

    @Test
    public void WhenAddingSameAssociationAgain_ThenNoException() {
        Recipe recipe = new Recipe();
        recipe.setTitle("A Recipe");
        recipe = recipeRepository.save(recipe);

        Cookbook cookbook = new Cookbook();
        cookbook.setTitle("A Cookbook");
        cookbook = cookbookRepository.save(cookbook);

        cookbook.addRecipe(recipe, "integrationtest", new Date());
        cookbook = cookbookRepository.save(cookbook);

        cookbook.removeRecipe(recipe);
        cookbook = cookbookRepository.save(cookbook);
        assertThat(cookbookRecipeRepository.findAll().size(), is(0));
    }

}

断言失败。我不明白为什么。

java.lang.AssertionError: 
Expected: is <0>
     but: was <1>

我希望JPA生成DELETE语句,因为orphanRemoval设置为true。相反,CookbookRecipe.recipeId和CookbookRecipe.cookbookId在数据库中设置为null,但不会删除它们。

1 个答案:

答案 0 :(得分:0)

我认为该实体未被删除,因为&#39; CookbookRecipe&#39;实体以2种方式映射@oneToMany,来自&#39; cookbook&#39;并且来自&#39; recipe&#39;这样你就不能从cookbook中删除配方,因为Spring数据接受来自2个实体的命令,你必须从配方中取消第二个confTOMany并且也能正常工作,你也可以在存储库中使用@query(编写自定义查询删除并将工作也好),一年前我遇到了这个问题,希望很有用,