如果必须保留对象的“复杂”结构,则@ManyToMany关联会出现问题。
我有一个使用Spring Boot(spring-boot-starter-parent 2.2.5.RELEASE)和JPA(spring boot-starter-data-jpa)的Java应用程序。作为数据库,我尝试使用MySQL和H2。效果是一样的。
摘要1
我在创建类别时使用的是List对象,而在创建项目对象时使用的是相同的List对象。
Category cat1 = new Category("Cat 1", personList, null);
categoryService.create(cat1);
Item item1 = new Item("Item 1", personList, cat1);
itemService.create(item1);
摘要2
我在创建类别时使用List对象,在创建项目对象时重用category.getManager列表。
Category cat2 = new Category("Cat 2", personList, null);
categoryService.create(cat2);
Item item2 = new Item("Item 2", cat2.getManagers(), cat2);
itemService.create(item2);
摘要3
现在,与摘要2中相同的过程进行了额外的分配和更新:
Category cat3 = new Category("Cat 3", personList, null);
categoryService.create(cat3);
cat3.setManagers(personList);
categoryService.update(cat3);
Item item3 = new Item("Item 3", cat3.getManagers(), cat3);
itemService.create(item3);
问题
为什么摘要2中的作业没有保留?
我没记账吗
我会误会幕后发生的事情吗?
这可能是一个特殊的用例,但是我是否需要重新阅读一些主题或章节?
斯图加特的问候
模型结构
Person.java:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// Constructors -------------------------------------------------------------------------------
public Person() {}
public Person(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
// Getters / Setters --------------------------------------------------------------------------
// ...
}
Category.java:
@Entity
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
private List<Person> managers;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name="category_id", updatable = false)
private List<Item> items;
// Constructors -------------------------------------------------------------------------------
public Category() {}
public Category(String name, List<Person> managers, List<Item> items) {
super();
this.name = name;
this.managers = managers;
this.items = items;
}
// Getters / Setters --------------------------------------------------------------------------
// ...
}
Item.java:
@Entity
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
private List<Person> persons;
@ManyToOne
private Category category;
// Constructors -------------------------------------------------------------------------------
public Item() {}
public Item(String name, List<Person> persons, Category category) {
super();
this.name = name;
this.persons = persons;
this.category = category;
}
// Getters / Setters --------------------------------------------------------------------------
// ...
}
财产人应包括分配给特定物品的人。属性类别应包含为多个项目分配的可选类别。类别的管理者将分配给其项目的人员属性。
非常简单直接。
相应的存储库接口和服务类
存储库接口扩展了JpaRepository接口。服务类包含用于创建,获取,更新和删除数据库中记录的方法。((我只是发布其中之一))
@Service
public class PersonService {
@Autowired
PersonRepository personRepo;
public Person create(Person person) {
return personRepo.save(person);
}
public Person get(long id) {
return personRepo.getOne(id);
}
public void update(Person person) {
personRepo.save(person);
}
public void delete(long id) {
personRepo.deleteById(id);
}
}
三个测试案例
这里的代码演示了在数据库中创建记录时遇到的问题。有3种情况。
@Component
public class DatabaseInitializer implements CommandLineRunner {
@Autowired
CategoryService categoryService;
@Autowired
ItemService itemService;
@Autowired
PersonService personService;
@Override
public void run(String... args) throws Exception {
// Create persons
Person max = personService.create(new Person("Max", "Mad"));
Person sally = personService.create(new Person("Sally", "Silly"));
Person bob = personService.create(new Person("Bob", "Bad"));
List<Person> personList = Arrays.asList(max, sally, bob);
// Case 1 (this works)
// Here we will use the personList object for both the category and the item.
Category cat1 = new Category("Cat 1", personList, null);
categoryService.create(cat1);
Item item1 = new Item("Item 1", personList, cat1);
itemService.create(item1);
// => The category record has three person records assigned as managers (see. category_managers table)
// => The item record has three person records assigned as persons (see. item_persons table)
// Case 2 (this doesn't work)
// Here we will use the personList object to create the category and reuse the category.getManager list to create the item.
Category cat2 = new Category("Cat 2", personList, null);
categoryService.create(cat2);
Item item2 = new Item("Item 2", cat2.getManagers(), cat2);
itemService.create(item2);
// => The category has no managers assignments (see. category_managers table) WHY??
// => The item has three person records assigned as persons (see. item_persons table)
// Case 3 (workaround of case 2)
// Here we will do the same as in case 2, but will do an extra assignment of the managers of the category
Category cat3 = new Category("Cat 3", personList, null);
categoryService.create(cat3);
cat3.setManagers(personList);
categoryService.update(cat3);
Item item3 = new Item("Item 3", cat3.getManagers(), cat3);
itemService.create(item3);
// => The category has three person records assigned as managers (see. category_managers table)
// => The item has three person records assigned as persons (see. item_persons table)
}
}
试图解释案例2
的属性的ArrayList对象managers
物体的cat2
匝数成调用方法时一个PersistentBag对象save(S entity)
的JpaRepository
或相当CrudRepository
。现在,此集合对象不仅包含Person
对象的实体。似乎该对象还表示实体Category
和之间的关系Person
。
另外要记住:Java对象变量指向内存堆中的对象。
因此,在这一行中Item item2 = new Item("Item 2", cat2.getManagers(), cat2);
,getManagers()
方法将对内存中真实对象的引用移交给实体的构造函数,Item
而不仅仅是值。
现在,当这个新实体必须存储在数据库中并且PersistentBag对象现在表示项目与其人员之间的关系时,它将像这样存储。这样关系将被移动。
解释情况3
该行cat3.setManagers(personList);
只是用ArrayList对象替换PersistentBag对象。
因为列表未更改,所以调用了更新,但是没有关系的更新,并且该getManagers()
方法未将对类别的PersistentBag的引用移交给内存,并且保持“不变”。
如果我们在更新之前更改List对象,则还将执行关系的更新:
Category cat3 = new Category("Cat 3", personList);
categoryService.create(cat3);
personList.add(personService.create(new Person("Mazy", "Lazy")));
cat3.setManagers(personList);
categoryService.update(cat3);
Item item3 = new Item("Item 3", cat3.getManagers(), cat3);
itemService.create(item3);
潜在的噩梦
这可能会有很多副作用。在这个简单的示例中,如果您知道这一点,则很容易调试。但是考虑一下散布在许多方法上的巨大代码。然后一种方法使用参数中的ArrayList进行更改或将其分配给其他实体,而无需了解原始实体或这是一个PersistentBag ...
一种解决方案
我要记住,不要仅使用方法的返回值getManagers()
。此外,我应该创建一个新的集合对象。
也许像这样:
Category cat2 = new Category("Cat 2", personList);
categoryService.create(cat2);
List<Person> itemPersonList = new ArrayList<>();
cat2.getManagers().forEach(itemPersonList::add);
Item item2 = new Item("Item 2", itemPersonList, cat2);
itemService.create(item2);
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句