removeRecipe
요리 책에 Cookbook
에서 Recipe
을 제거합니다. 참조를 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>
orphanRemoval이 true로 설정되어 있기 때문에 JPA에서 DELETE 문을 생성 할 것으로 예상합니다. 대신 CookbookRecipe.recipeId 및 CookbookRecipe.cookbookId는 데이터베이스에서 null로 설정되지만 제거되지는 않습니다.
'RecipeRepository.delete (Recipe)'와 같은 것을 호출하여'Recipe' 엔티티를 삭제해야합니다. 'null'에 대한 참조 설정이 충분하지 않습니다. –
레시피를 삭제하고 싶지 않습니다. 요리 책에서 요리법을 삭제하고 싶습니다. 따라서 협회를 제거해야합니다. 그러나 JPA는 orphanRemoval이 true 일 때라도 필요한 DELETE 문을 작성하지 않습니다. 대신 값은 null로 설정됩니다. 이는 충분하지만 충분하지 않습니다. – kfaria
나는 답을 알지 못한다. 그러나 무엇이 대체 된 equalsByBusinessKey (...)가 무엇인지 알기에 궁금하다. – FakirTrappedInCode