【问题标题】:How to persist @ManyToMany relation - duplicate entry or detached entity如何保持@ManyToMany 关系 - 重复条目或分离实体
【发布时间】:2012-04-06 23:49:29
【问题描述】:

我想将我的实体与 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

第一次,当我尝试使用 UserRoles“ROLE_USER”来持久化用户时,没问题。插入了用户和用户角色以及连接表。

我的问题是当我尝试使用相同的 UserRoles 保留第二个用户时。 我通过找到它来检查 UserRoles 是否存在 (userRolesServices.getUserRoleByName(...))。 如果存在 -> 将此 UserRoles 添加到用户列表(id + 角色名称),否则我创建一个新的(仅角色名称)。

当我尝试保留第二个用户时,我得到以下异常: “要持久化的分离实体:.....UserRoles”(可能是因为 getUserRoleByName 在另一个事务中执行)

如果我不使用 getUserRoleByName(仅 *new UserRoles("ROLE_USER");*),我会得到以下异常: "...ConstraintViolation : 'ROLE_NAME' 的重复条目..."

那么,如何正确持久化具有 @ManyToMany 关系的实体?

【问题讨论】:

    标签: java jpa many-to-many unique-constraint duplicates


    【解决方案1】:

    对于上述问题,我会说您的实体关系级联是错误的。考虑一下:一个用户可以有多个角色,但系统中可以存在固定数量的角色。所以 User 实体的 CASCADE ALL 没有任何意义,因为 UserRoles 的生命周期不应依赖于 User 实体生命周期。例如。当我们删除 User 时,UserRoles 不应该被删除。

    要持久化的分离实体异常只会在您传递已将主键设置为持久化的对象时发生。

    删除级联,您的问题现在将得到解决,您只需决定如何插入用户角色。在我看来,这样做应该有单独的功能。

    也不要使用ArrayList,使用HashSetArrayList 允许重复。

    【讨论】:

    • 我可以从级联类型中删除 REMOVE : cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} ?但我认为我总是在持久化上获得“唯一约束违规”......(没有 id)......如果我手动删除级联并持久化 UserRoles,我该如何修改连接表?这种关系有什么用处?好的HashSet,我会修改它...
    • 使用 cascade = CascadeType.MERGE 有效!谢谢你的帮助。
    • @Aure77,我和你有同样的问题。我可以将级联设置为 MERGE,但是当它们是新的时,将不再创建 UserRoles ......当休眠尝试插入引用用户角色表上不存在的行的连接表时,我将遇到约束冲突.那么,我错过了什么吗?谢谢!
    • 我已经去掉了级联,我的问题就解决了,就像你说的一样!
    【解决方案2】:

    如果有人对我和作者遇到相同类型的问题,我会提供答案。

    基本上我面临的情况是我有一个表是某种 CONSTANT 值。另一个会改变,但它应该将 (many to many) 映射到那些 CONSTANTS

    确切的问题是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 Docs 中所述

    如果您选择在两个方向上映射关系,那么一个 方向必须定义为所有者,而另一个必须使用 mappedBy 属性来定义其映射(...)

    • cascade = {CascadeType.MERGE} 添加用于适当的级联 JPA Docs

    级联 EntityManager.merge() 操作。如果调用了 merge() 父母,那么孩子也将被合并。这通常应该 用于依赖关系。请注意,这只影响 级联合并,关系引用本身将始终 合并。

    【讨论】:

      【解决方案3】:

      (可能是因为getUserRoleByName是在另一个事务中执行的)

      这似乎是问题所在,请在同一个事务/实体管理器中进行查询。否则使用 find() 在当前事务中重新查找。

      【讨论】:

      • 我有 2 个服务:UserServices 和 UserRolesServices。每个人在事务(spring @Transactional)中都有获取(查找)方法。那么,如果我在 UserService 持久化方法中调用 UserRolesServices find 方法,它应该在同一个事务中执行吗?
      • 经过测试并不能按预期工作......因为每个服务都有自己的事务(我认为)......总是“分离实体持续存在”
      【解决方案4】:

      重复原因:ID 是自动生成的,因此每次都会创建新角色。 这样使用:

      用户

      @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 Channel Atquil

      【讨论】:

        【解决方案5】:

        我有同样的问题,但还没有解决。 我的 RelationShip 是 HotelDeliveryPartners。 以下是类:

        @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 表)中使用。 现在不知道该怎么做。

        【讨论】:

          【解决方案6】:

          我遇到了同样的错误,但在为关系的双方添加 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);
           
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2015-01-30
            • 1970-01-01
            • 1970-01-01
            • 2022-01-12
            • 2011-09-27
            • 1970-01-01
            • 2011-10-28
            • 2021-06-04
            相关资源
            最近更新 更多