Как сохранить отношение @ManyToMany - повторяющаяся запись или отдельный объект

Я хочу сохранить свою сущность с отношением ManyToMany. Но у меня есть проблема во время постоянного процесса.

Мои сущности:

@Entity
@Table(name = "USER")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userId;

    @Column(name = "NAME", unique = true, nullable = false)
    String userName;

    @Column(name = "FORNAME")
    String userForname;

    @Column(name = "EMAIL")
    String userEmail;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "USER_USER_ROLES", joinColumns = @JoinColumn(name = "ID_USER"), inverseJoinColumns = @JoinColumn(name = "ID_ROLE"))
    List<UserRoles> userRoles = new ArrayList<UserRoles>();

    // getter et setter
}

а также

@Entity
@Table(name = "USER_ROLES")
public class UserRoles implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long userRolesId;

    @Column(unique = true, nullable = false, name = "ROLE_NAME")
    String roleName; 

    // getter et setter
}

Сервисный код:

User user = new User();
UserRoles role;
try {
    role = userRolesServices.getUserRoleByName("ROLE_USER"); // find jpql - transaction
} catch (RuntimeException e) {
    LOGGER.debug("No Roles found");
    role = new UserRoles("ROLE_USER"); // create new
}
user.addUserRole(role);
user.setUserName(urlId);
user.setUserForname(fullName);
user.setUserEmail(email);
userServices.createUser(user); // em.persist(user) - transaction

В первый раз, когда я пытаюсь сохранить пользователя с ролями пользователей «ROLE_USER», проблем нет. Вставляются User и UserRoles, а также таблицы соединений.

Моя проблема в том, что я пытаюсь сохранить второго пользователя с теми же UserRoles. Я проверяю, существует ли UserRoles, найдя его (userRolesServices.getUserRoleByName (...)). Если существует -> добавьте эти роли пользователей в список пользователей (идентификатор + имя роли), иначе я создам новый (только имя роли).

Когда я пытаюсь сохранить второго пользователя, я получаю следующее исключение: «Отдельный объект для сохранения: ..... UserRoles» (возможно, потому, что getUserRoleByName выполняется в другой транзакции)

Если я не использую getUserRoleByName (только * new UserRoles ("ROLE_USER"); *), я получаю следующее исключение: "... ConstraintViolation: повторяющаяся запись для 'ROLE_NAME' .. . "

Итак, как правильно сохранить объект с отношением @ManyToMany?


person Aure77    schedule 22.03.2012    source источник


Ответы (6)


Для вышеуказанной проблемы я бы сказал, что ваш каскад отношений сущностей неверен. Учтите следующее: у пользователя может быть несколько ролей, но может быть фиксированное количество ролей, которые могут существовать в системе. Таким образом, КАСКАД ВСЕ из User сущности не имеет никакого смысла, поскольку жизненный цикл UserRoles не должен зависеть от жизненного цикла User сущности. Например. когда мы удаляем User, UserRoles не должен удаляться.

Исключение отсоединенный объект для сохранения возникнет только в том случае, если вы передаете объект, для которого уже установлен первичный ключ.

Удалите каскад, и ваша проблема будет решена, теперь вам нужно только решить, как вы собираетесь вставлять роли пользователей. По моему мнению, для этого должен быть отдельный функционал.

Также не используйте ArrayList, используйте HashSet. ArrayList допускает дубликаты.

person Amit Deshpande    schedule 24.03.2012
comment
Я могу удалить УДАЛИТЬ из каскадного типа: cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}? Но я думаю, что всегда получаю уникальное нарушение ограничения при сохранении ... (без идентификатора) ... И если я удалю каскад и сохраню UserRoles вручную, как я могу изменить таблицу соединений? И в чем польза этих отношений? Хорошо для HashSet, я его изменю ... - person Aure77; 26.03.2012
comment
Использование cascade = CascadeType.MERGE работает! спасибо за помощь. - person Aure77; 28.03.2012
comment
@ Aure77, у меня такая же проблема, как и у вас. Я могу установить каскад на MERGE, но тогда UserRoles больше не будут создаваться, когда они новые ... и я получу нарушение ограничения, когда hibernate пытается вставить в таблицу соединений, ссылаясь на несуществующую строку в таблице ролей пользователей . Итак, я что-то пропустил? Спасибо! - person user683887; 28.09.2012
comment
Я удалил каскад, и моя проблема решена, как вы и сказали! - person naXa; 17.05.2017

Я отвечу, если у меня и у автора возникнут одинаковые проблемы.

По сути, я столкнулся с ситуацией, когда у меня была одна таблица, в которой были какие-то значения CONSTANT. Другой вариант изменится, но он должен соответствовать (many to many) этим КОНСТАНТАМ.

Точная проблема USERS, а это ROLES.

Диаграмма

Roles будут известны и добавлены при запуске системы, поэтому их нельзя удалять. Даже если ни у одного пользователя не будет Role, оно должно все еще присутствовать в системе.

Реализация класса с использованием JPA:

Пользователь:

@Entity
@Table(name = "USERS")
public class User{

    @Id
    private String login;
    private String name;
    private String password;

    @ManyToMany(cascade = {CascadeType.MERGE})
    private Set<Role> roles = new HashSet<>();

Роль:

@Entity
@Table(name = "ROLE")
public class Role {

    @Id
    @Enumerated(value = EnumType.STRING)
    private RoleEnum name;

    @ManyToMany(mappedBy = "roles")
    private Set<User> users = new HashSet<>();

Использование

Эта установка легко добавит / удалит Role в User. Просто передав массив, например: user.getRoles().add(new Role("ADMIN")); и merge, user. Удаление работает с передачей пустого списка.

Если вы забудете добавить Role перед добавлением его пользователю, скорее всего, вы получите сообщение об ошибке:

javax.persistence.RollbackException: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: com.storage.entities.Role@246de37e.

Что и почему

  • Атрибут mappedBy добавляется к дочернему объекту, как описано в Документах JPA

Если вы выбрали отображение отношения в обоих направлениях, тогда одно направление должно быть определено как владелец , а другое должно использовать атрибут mappedBy для определения своего сопоставления (...)

  • cascade = {CascadeType.MERGE} добавлен для правильных каскадов JPA Docs

Каскадирование операции EntityManager.merge (). Если для родителя вызывается merge (), то дочерний элемент также будет объединен. Обычно это следует использовать для зависимых отношений. Обратите внимание, что это влияет только на каскадирование слияния, сама ссылка на связь всегда будет слита.

person Atais    schedule 30.03.2016

(возможно, потому, что getUserRoleByName выполняется в другой транзакции)

Казалось бы, проблема, выполните запрос в том же диспетчере транзакций / сущностей. В противном случае повторно найдите его в текущей транзакции с помощью find ().

person James    schedule 22.03.2012
comment
У меня есть 2 сервиса: UserServices и UserRolesServices. У каждого из них есть метод get (find) в транзакции (spring @Transactional). Итак, если я вызываю метод поиска UserRolesServices в методе сохранения UserService, должен ли он выполняться в той же транзакции? - person Aure77; 22.03.2012
comment
Протестировано и работает не так, как ожидалось ... потому что у каждой службы есть собственная транзакция (я думаю) ... Всегда отсоединенный объект для сохранения - person Aure77; 23.03.2012

Причина дублирования: идентификатор создается автоматически, поэтому каждый раз при создании новой роли. Используйте этот способ:

Пользователь

@Entity
@Table(name = "user")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int user_Id;

@Column(name = "email")
private String email;

@Column(name = "firstname")
private String firstname; 

@Column(name = "lastname")
private String lastname;

@Column(name = "password")
private String password;

@Column(name = "active")
private int active;

@ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
@JoinTable(name="user_role", 
    joinColumns=@JoinColumn(name="user_Id"),
    inverseJoinColumns=@JoinColumn(name="role_Id"))
private Set<Role> roles  = new HashSet<>();
//Getter and Setter

Роль

@Entity
@Table(name="roles")
public class Role {

 @Id
 @Column(name="role_Id")
 private int role_Id;

 @Column(name="role_name")
 private String role_name;

 @ManyToMany(mappedBy = "roles")
 private Set<User> users= new HashSet<>();

Контроллер (должен был добавить его в службу)

 @PutMapping("/addEmp")
   public String addEmp(@RequestBody User user) {

    String pass=passencoder.encode(user.getPassword());
    user.setPassword(pass);
    List<Role> roles =rolerepo.findAll();
    for(Role role: roles) 
        System.out.println("Roles"+ role.getRole_name());
    //user.setRoles(new HashSet < > (rolerepo.findAll()));
    userrepo.save(user);


    return "User Created";
}

Вывод  введите описание изображения здесь

Роли  введите описание изображения здесь

Роль_пользователя  введите описание изображения здесь

Если вам понравился ответ, подпишитесь на канал YouTube Atquil.

person ATUL ANAND    schedule 11.06.2020

У меня такая же проблема, но я пока не могу ее решить. Моя RelationShip - это Hotel к DeliveryPartners. Ниже приведены классы:

@Entity Class 

package com.hotelapp.models;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.Set;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Hotel {

    @Id
    @GeneratedValue(generator = "hotel_id", strategy = GenerationType.AUTO)
    @SequenceGenerator(name = "hotel_id", sequenceName = "hotel_id")
    private Integer hotelId;
    private String hotelName;
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "address_id")
    private Address address;
    @OneToMany(cascade =  CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "hotel_id")
    private Set<Menu> menuList;
    @ManyToMany(cascade = {CascadeType.MERGE} ,fetch = FetchType.EAGER)
    @JoinTable(name ="hotel_delivery", joinColumns = @JoinColumn(name ="hotel_id"), inverseJoinColumns = @JoinColumn(name="delivery_id"))
    private Set<Delivery> delivery;

    public Hotel(String hotelName, Address address, Set<Menu> menu, Set<Delivery> delivery) {
        this.hotelName = hotelName;
        this.address = address;
        this.menuList = menu;
        this.delivery = delivery;
    }

    @Override
    public String toString() {
        return "Hotel{" +
                "hotelName='" + hotelName + '\'' +
                ", address=" + address +
                ", menu=" + menuList +
                ", delivery=" + delivery +
                '}';
    }
}

@Delivery Class

package com.hotelapp.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Delivery {

    @Id
    @GeneratedValue(generator = "del_id", strategy = GenerationType.AUTO)
    @SequenceGenerator(name = "del_id", sequenceName = "delivery_id")
    private Integer deliveryId;
    private String partnersName;
    private Double charges;
    @ManyToMany(mappedBy = "delivery", cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
    @JsonIgnore
    private Set<Hotel> hotelList = new HashSet<>();

    public Delivery(String partnersName, Double charges) {
        this.partnersName = partnersName;
        this.charges = charges;
    }

    @Override
    public String toString() {
        return "Delivery{" +
                "partnersName='" + partnersName + '\'' +
                ", charges='" + charges + '\'' +
                '}';
    }
}

@Controller Класс

  @PostMapping("/hotels")
    public ResponseEntity<Hotel> addHotel(@RequestBody Hotel hotel){
        Hotel hotel1 =hotelService.addHotel(hotel);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("desc", "oneHotelAdded");
        return ResponseEntity.ok().headers(httpHeaders).body(hotel1);
    }

Когда я использую каскадный тип слияния, получаю следующее исключение:

Hibernate: insert into address (city, state, street_name, zip_code, address_id) values (?, ?, ?, ?, ?)
Hibernate: insert into hotel (address_id, hotel_name, hotel_id) values (?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into menu (hotel_id, menu_name, price, menu_id) values (?, ?, ?, ?)
Hibernate: insert into hotel_delivery (hotel_id, delivery_id) values (?, ?)
2020-07-12 00:13:37.973 INFO 50692 --- [nio-9098-exec-1] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2020-07-12 00:13:38.026 ERROR 50692 --- [nio-9098-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - **save the transient instance before flushing: com.hotelapp.models.Delivery; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hotelapp.models.Delivery] with root cause
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.hotelapp.models.Delivery**
at org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:347) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:495) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.type.EntityType.nullSafeSet(EntityType.java:280) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.writeElement(AbstractCollectionPersister.java:930) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1352) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:52) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_221]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102) ~[hibernate-core-5.4.17.Final.jar:5.4.17.Final]

Я понимаю, что из части запросов нет запроса на вставку для таблицы доставки, поэтому доставку можно использовать в hotel_delivery (таблица MTM). Не знаю, как действовать сейчас.

person Shubham    schedule 12.07.2020

Я получал ту же ошибку, но после добавления cascade = CascadeType.ALL для обеих сторон отношения проблема решена. раньше я был cascade = CascadeType.ALL только на родительской стороне отношения, после добавления дочернего элемента код теперь работает нормально. вот мой код.

Читатель (родительский объект):

  @ManyToMany(cascade = CascadeType.PERSIST)
        @JoinTable(name="READER_SUBSCRIPTIONS", joinColumns= 
         {@JoinColumn(referencedColumnName="ID")}
            , inverseJoinColumns={@JoinColumn(referencedColumnName="ID")})
  private List<Subscription> subscriptions;

Подписка (дочерняя сущность):

  @ManyToMany(mappedBy="subscriptions", cascade = CascadeType.ALL)
  private Set<Reader> readers;

Код стойкости:

List<Subscription> list = new ArrayList<Subscription>();
list.add(sub1);
list.add(sub2);

Set<Reader> readerSet = new HashSet<Reader>();
readerSet.add(reader1);
readerSet.add(reader2);

reader1.setSubscriptions(list);
reader1.setSubscriptions(list);

sub1.setReaders(readerSet);
sub2.setReaders(readerSet);

reader2.setSubscriptions(list);
reader2.setSubscriptions(list);

readSubscriberRepository.save(reader1);
readSubscriberRepository.save(reader2);
 
person mb66    schedule 24.06.2021