有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何持久化@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”持久化一个用户时,没有问题。将插入用户和用户角色以及联接表

我的问题是,当我试图持久化具有相同用户角色的第二个用户时。 我通过查找userRolesServices.getUserRoleByName(…)来检查UserRoles是否存在)。 如果存在->;将此UserRoles添加到用户列表(id+角色名),否则我将创建一个新的(仅角色名)

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

如果我不使用getUserRoleByName(仅*新用户角色(“角色用户”);*),我获得以下例外: “……约束约束:重复输入‘角色名称’…”

那么,如何正确地保持一个具有@manytomy关系的实体呢


共 (6) 个答案

  1. # 1 楼答案

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

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

    移除cascade,您的问题就会得到解决,现在您唯一需要决定的是如何插入用户角色。根据我的说法,这样做应该有单独的功能

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

  2. # 2 楼答案

    如果有人向我和作者提出同样的问题,我会给出我的答案

    基本上,我所面临的情况是,当我有一张表时,它是某种常量值。另一个会改变,但它应该映射(many to many)到那些常数

    确切的问题是USERS,它是ROLES

    Diagram

    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。只需传递一个数组,f.e.:user.getRoles().add(new Role("ADMIN"));mergeuser删除可用于传递空列表

    如果在将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中所述

    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 (...)

    • ^添加{}用于适当的级联{a3}

    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.

  3. # 3 楼答案

    我也有同样的问题,但还没解决。 我的关系是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表)中使用送货。 我现在不知道该怎么办

  4. # 4 楼答案

    (maybe because getUserRoleByName is performed in another transaction)

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

  5. # 5 楼答案

    复制原因: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";
    }
    

    输出 enter image description here

    角色 enter image description here

    用户角色 enter image description here

    如果你喜欢答案,请订阅Youtube Channel Atquil

  6. # 6 楼答案

    我得到了同样的错误,但是在添加了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);