Spring JPA: отношение ManyToMany не сохраняется в базе данных при повторном использовании свойства для хранения другого объекта - PullRequest
0 голосов
/ 20 марта 2020

У меня проблема с ассоциацией @ManyToMany, если необходимо сохранить «сложную» структуру объектов.

У меня есть Java приложение, использующее Spring Boot (spring-boot) -starter-parent 2.2.5.RELEASE) с JPA (пружинный загрузчик-стартер-данные-jpa). В качестве базы данных я пробовал MySQL и H2. Эффект тот же.

Аннотация 1

Я использую объект List при создании категории и использую тот же объект List при создании объекта item.

Category cat1 = new Category("Cat 1", personList, null);
categoryService.create(cat1);
Item item1 = new Item("Item 1", personList, cat1);
itemService.create(item1);
  • В записи категории есть три записи о лицах, назначенные в качестве менеджеров в таблице category_managers
  • В записи элемента есть три записи о лицах, назначенные как лица в таблице item_persons

Аннотация 2

Я использую объект List при создании категории и повторно использую список category.getManager при создании объекта item.

Category cat2 = new Category("Cat 2", personList, null);
categoryService.create(cat2);
Item item2 = new Item("Item 2", cat2.getManagers(), cat2);
itemService.create(item2);

  • Элемент имеет три записи о людях, назначенных в качестве персон в таблице item_persons
  • !!! В категории нет назначений менеджеров в таблице category_managers

Аннотация 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);
  • В категории есть записи из трех человек, назначенные в качестве менеджеров (см. Таблицу category_managers)
  • Для элемента есть записи из трех человек, назначенные в качестве лиц (см. Таблицу item_persons)

Вопросы

Почему задания в Резюме 2 не сохранились?
Разве я не учел что-то?
Не понимаю ли я что-то, что происходит за сцена?

Это может быть особый случай использования, но есть ли какие-то темы или главы, которые мне нужно прочитать снова?

Привет из Штутгарта


Подробное описание

Структура модели

Перс. 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 --------------------------------------------------------------------------

    // ...

}

Категория. 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 --------------------------------------------------------------------------

    // ...
}

Имущество должно быть продолжено ain лица, назначенные для определенного элемента. Категория свойства должна содержать необязательную категорию, назначенную для нескольких элементов. Менеджерам категории будет назначено свойство person ее элементов.

Очень просто и понятно.

Соответствующие интерфейсы репозитория и классы обслуживания

Интерфейсы репозитория расширяют интерфейс 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 ]

0 голосов
/ 25 марта 2020

Попытка объяснить случай 2

Объект ArrayList свойства managers объекта cat2 превращается в объект PersistentBag при вызове метода save(S entity) of JpaRepository а точнее CrudRepository. Теперь этот объект коллекции содержит не только объекты Person объектов. Похоже, этот объект также представляет отношение между сущностями Category и Person.

Также следует иметь в виду: Java переменные объекта указывают на объект в куче памяти.
Поэтому в строке Item item2 = new Item("Item 2", cat2.getManagers(), cat2); метод getManagers() передает ссылку на реальный объект в памяти в конструктор объекта Item, а не только на значения.
Теперь, когда этот новый объект должен быть хранится в базе данных, и объект PersistentBag теперь представляет отношение между элементом и его лицами, это будет сохранено так. Таким образом, отношение будет перемещено.

Чтобы объяснить случай 3

Строка cat3.setManagers(personList); просто заменяет объект PersistentBag на объект ArrayList.
Поскольку список не был изменен, обновление было вызвано, но не было обновления отношений, и метод getManagers() не передает ссылку на PersistentBag категории ie в памяти и сохраняет «нетронутым».

Если мы изменим объект 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);
0 голосов
/ 20 марта 2020

Я собираюсь go перейти непосредственно к случаю 2. Это не работает, потому что это не в транзакции. Когда вы создаете сущность: new Category("Cat 2", personList, null); эта сущность отсоединяется. Затем вы вызываете транзакционный метод categoryService.create(cat2); и передаете ему категорию. В транзакции сущность становится постоянной, и попытка заставить менеджеров работать , однако после завершения транзакции и обновления базы данных, и мы go возвращаемся к run() методу , мы не дольше в этой транзакции . Объект cat2 создается вне этой транзакции и не обновляется после завершения транзакции. Если вы хотите иметь возможность доступа к cat2.getManagers() тем же методом, я предлагаю сделать метод run() транзакционным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...