How to persist @ManyToMany relation - duplicate entry or detached entity

后端 未结 5 1831
一整个雨季
一整个雨季 2020-12-01 06:36

I want to persist my entity with ManyToMany relation. But i have some problem during persisting process.

My entities :

@Entity
@Table(name = \"USER\"         


        
相关标签:
5条回答
  • 2020-12-01 07:13

    I have the same issue, but couldn't get through it yet. My RelationShip is Hotel to DeliveryPartners. Following are the classes:

    @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 Class

      @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);
        }
    

    When I use merge cascade type, getting following exception:

    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]
    

    I realise from the queries part there is no insert query for delivery table so that delivery can be used in hotel_delivery (MTM Table). Don't know how to proceed now.

    0 讨论(0)
  • 2020-12-01 07:15

    For above problem I would say your entity relationship cascade is wrong. Consider this: A user can have multiple roles but there can be fixed number of roles that can exist in the system. So CASCADE ALL from User entity does not make any sense, since life cycle of UserRoles should not depend on User entity life cycle. E.g. when we remove User, UserRoles should not get removed.

    detached entity to persist exception will only occur when you are passing object which has primary key already set to persist.

    Remove cascade and your problem will be solved now only thing you will need to decide is how you are going to insert User roles. According to me there should be separate functionality to do so.

    Also do not use ArrayList, use HashSet. ArrayList allows duplicates.

    0 讨论(0)
  • 2020-12-01 07:17

    I will provide my answer if anyone get same type of problem to me and the author.

    Basically what I was facing was a situation when I had one table which was some kind of CONSTANT values. And the other would change, but it should map (many to many) to those CONSTANTS.

    The exact problem is USERS and it's ROLES.

    Roles would be known and added on system startup, thus they should never get removed. Even if no user would have some Role it should still be in the system.

    The class implementation, using JPA:

    User:

    @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<>();
    

    Role:

    @Entity
    @Table(name = "ROLE")
    public class Role {
    
        @Id
        @Enumerated(value = EnumType.STRING)
        private RoleEnum name;
    
        @ManyToMany(mappedBy = "roles")
        private Set<User> users = new HashSet<>();
    

    Usage

    This setup will easily add/remove Role to User. Simply by passing an array, f.e.: user.getRoles().add(new Role("ADMIN")); and merge the user. Removing works with passing an empty list.

    If you forget to add the Role before adding it to the user most likely you will get an error like:

    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.
    

    What and why

    • mappedBy attribute is added to the child Entity as described in the JPA Docs

    If you choose to map the relationship in both directions, then one direction must be defined as the owner and the other must use the mappedBy attribute to define its mapping (...)

    • cascade = {CascadeType.MERGE} is added for proper cascades JPA Docs

    Cascaded the EntityManager.merge() operation. If merge() is called on the parent, then the child will also be merged. This should normally be used for dependent relationships. Note that this only affects the cascading of the merge, the relationship reference itself will always be merged.

    0 讨论(0)
  • 2020-12-01 07:27

    Duplication Reason: Id is autogenerated, so every time a new role is being created. Use in this way :

    User

    @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
    

    Role

    @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<>();
    

    Controller (Should have added it to Service)

     @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";
    }
    

    Output

    Roles

    User_Role

    If you liked the answer please subscribeYoutube Channel Atquil

    0 讨论(0)
  • 2020-12-01 07:33

    (maybe because getUserRoleByName is performed in another transaction)

    That would seem to the the issue, do the query in the same transaction/entity manager. Otherwise re-find it in the current transaction using find().

    0 讨论(0)
提交回复
热议问题