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