Как правильно сделать ManyToMany c дополнительными колонками?
Идея проста: я хочу сделать зависимость типа ManyToMany, но с дополнительной колонкой.
В своем проекте я использую Project Lombok и поэтому @Data заменяет мне Getter/Setter/ToString/Equals/Hash.
Моя идея на 100% схожа с: https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/ Однако данный вариант у меня не работает
Entity Relational Diagram:
Ошибка происходит во время тестирования программы:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
(`dishdatabase`.`dish_size`, CONSTRAINT `FKqpfda2uojtpf1u243dy0e55fq` FOREIGN KEY (`product_id`) REFERENCES `product` (`id`))
Update 1 (25.апр.2020):
Проект перенес на гитхаб: https://github.com/Antonio112009/Many-To-Many_with_extra_column
Нижняя ошибка решилась, однако появилась другая:
я могу соединять один объект c другим, но я не могу соединить несколько объектов одного типа с одним другим. Проблема и ошибка описана на гитхабе.
org.springframework.orm.jpa.JpaSystemException: Could not set field value [SHORT_CIRCUIT_INDICATOR] value by reflection : [class com.antonio112009.manyToMany.entity.PostTagId.tagId]
setter of com.antonio112009.manyToMany.entity.PostTagId.tagId; nested exception is org.hibernate.PropertyAccessException: Could not set field value [SHORT_CIRCUIT_INDICATOR]
value by reflection : [class com.antonio112009.manyToMany.entity.PostTagId.tagId] setter of com.antonio112009.manyToMany.entity.PostTagId.tagId
Внизу представлен код, который я пытался реализовать в своем проекте:
Product.java:
@Entity
@Data
@Table
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_generator")
@SequenceGenerator(name="product_generator", sequenceName = "seq_product", allocationSize = 1)
private Long id;
@Column
private String name;
@OneToMany(
mappedBy = "product",
cascade = CascadeType.ALL,
orphanRemoval = true
)
List<DishSize> dishSizes = new ArrayList<>();
public void addDishSize(DishSize dishSize) {
dishSizes.add(dishSize);
dishSize.setProduct(this);
}
public void removeDishSize(DishSize dishSize) {
dishSizes.remove(dishSize);
dishSize.setProduct(null);
}
}
DishSize.java
@Entity
@Data
@Table
public class DishSize implements Serializable {
@Id
@GeneratedValue
private Long id;
private Integer size;
@ManyToOne(fetch = FetchType.LAZY)
private Product product;
@ManyToOne(fetch = FetchType.LAZY)
private Dish dish;
}
Dish.java
@Entity
@Data
@Table
public class Dish {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "dish_generator")
@SequenceGenerator(name="dish_generator", sequenceName = "seq_dish", allocationSize = 1)
private Long id;
@Column
private String name;
@OneToMany(
mappedBy = "dish",
cascade = CascadeType.ALL,
orphanRemoval = true
)
List<DishSize> dishSizes = new ArrayList<>();
public void addDishSize(DishSize dishSize) {
dishSizes.add(dishSize);
dishSize.setDish(this);
}
public void removeDishSize(DishSize dishSize) {
dishSizes.remove(dishSize);
dishSize.setDish(null);
}
Тест, который моя программа не проходит:
DishRepositoryTest.java
@SpringBootTest
class DishRepositoryTest {
@Autowired
private DishRepository dishRepository;
@Autowired
private ProductRepository productRepository;
private String name = "Цезарь";
@BeforeEach
void setUp() {
productRepository.deleteAll();
dishRepository.deleteAll();
Dish dish = new Dish();
dish.setName(name);
dish.setNotes("тест");
dishRepository.save(dish);
}
@Test
@Transactional
void deleteProductButNotDish(){
String nameProduct = "яблоко";
Integer size = 250;
Product product = new Product();
product.setName(nameProduct);
productRepository.save(product);
product = productRepository.findByNameAndSort(nameProduct, null);
Dish dish = dishRepository.findByName(name);
DishSize dishSize = new DishSize();
dishSize.setDish(dish);
dishSize.setSize(size);
product.addDishSize(dishSize);
dish.addDishSize(dishSize);
dishRepository.save(dish);
//Test
product = productRepository.findByNameAndSort(nameProduct, null);
product.getDishSizes().get(0).getDish().removeDishSize(product.getDishSizes().get(0));
productRepository.save(product);
productRepository.deleteByNameAndSort(nameProduct, null);
dish = dishRepository.findByName(name);
assertNotNull(dish, "checking if dish is not deleted");
assertNull(productRepository.findByNameAndSort(nameProduct, null), "checking if product is deleted");
assertEquals(0, dish.getDishSizes().size(), "checking if dishSize is deleted");
}
}
Ответы (1 шт):
Пример на Github
Рабочий пример @ManyToMany c дополнительной колонкой можно найти тут
На данный момент есть проблема с удалением объекта, в который вложен один или несколько объектов.
Update 1
Проблема с удалением нескольких объектов почти решена. Осталось добавить каскадное удаление объектов.
Основные требования:
- Не использовать
@DataLombok'a. Проблема связана с@EqualsAndHashCode. Ссылки на источники о данной вещи найдете в этом вопросе equals,addTag/Post,removeTag/Postдолжны быть записаны так же как и в примере ниже. Без них невозможно соединение двух сущностей.
Правильная запись кода
Dish.java
@Entity
@Getter
@Setter
@Table
public class Dish{
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "dish_generator")
@SequenceGenerator(name="dish_generator", sequenceName = "seq_dish", allocationSize = 1)
private Long id;
@Column
private String name;
@OneToMany(
mappedBy = "dish",
cascade = CascadeType.ALL,
orphanRemoval = true
)
List<DishSize> dishSizes = new ArrayList<>();
public void addProduct(Product product, Integer size) {
DishSize dishSize = new DishSize(product,this,size);
dishSizes.add(dishSize);
product.getDishSizes().add(dishSize);
}
public void removeProduct(Product product) {
for (Iterator<DishSize> iterator = dishSizes.iterator(); iterator.hasNext(); ) {
DishSize dishSize = iterator.next();
if (dishSize.getDish().equals(this) &&
dishSize.getProduct().equals(product)) {
iterator.remove();
dishSize.getProduct().getDishSizes().remove(dishSize);
dishSize.setProduct(null);
dishSize.setDish(null);
}
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Dish other = (Dish) obj;
return id != null && id.equals(other.getId());
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
DishSize.java
@Entity
@Getter
@Setter
@Table
public class DishSize {
@EmbeddedId
private DishSizePK id;
@Column
private Integer size;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("productId")
private Product product;
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("dishId")
private Dish dish;
public DishSize() {}
public DishSize(Product product, Dish dish, Integer size) {
this.product = product;
this.dish = dish;
this.id = new DishSizePK(product.getId(), dish.getId());
this.size = size;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
DishSize that = (DishSize) o;
return Objects.equals(product, that.product) &&
Objects.equals(dish, that.dish);
}
@Override
public int hashCode() {
return Objects.hash(product, dish);
}
}
DishSizePK.java
@Embeddable
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class DishSizePK implements Serializable {
@Column
private Long productId;
@Column
private Long dishId;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
DishSizePK that = (DishSizePK) o;
return Objects.equals(productId, that.productId) &&
Objects.equals(dishId, that.dishId);
}
@Override
public int hashCode() {
return Objects.hash(productId, dishId);
}
}
Product
@Entity
@Getter
@Setter
@ToString
@Table
public class Product{
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_generator")
@SequenceGenerator(name="product_generator", sequenceName = "seq_product", allocationSize = 1)
private Long id;
@Column
private String name;
@OneToMany(
mappedBy = "product",
cascade = CascadeType.ALL,
orphanRemoval = true
)
List<DishSize> dishSizes = new ArrayList<>();
public void addDish(Dish dish, Integer size) {
DishSize dishSize = new DishSize(this,dish,size);
dishSizes.add(dishSize);
dish.getDishSizes().add(dishSize);
}
public void removeDish(Dish dish) {
for (Iterator<DishSize> iterator = dishSizes.iterator(); iterator.hasNext(); ) {
DishSize dishSize = iterator.next();
if (dishSize.getProduct().equals(this) &&
dishSize.getDish().equals(dish)) {
iterator.remove();
dishSize.getDish().getDishSizes().remove(dishSize);
dishSize.setProduct(null);
dishSize.setDish(null);
}
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Product other = (Product) obj;
return id != null && id.equals(other.getId());
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
