如何持久化@ManyToMany关系 - 重复条目或分离实体

时间:2012-03-22 12:05:00

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

我想用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持久保存第二个用户时。 我通过找到它来检查UserRoles是否存在( userRolesServices.getUserRoleByName(...))。 如果存在 - &gt;将此UserRoles添加到用户列表(id +角色名称),否则我创建一个新的(仅角色名称)。

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

如果我不使用 getUserRoleByName (仅* new UserRoles(“ROLE_USER”); *),我将获得以下异常: “...... ConstraintViolation:'ROLE_NAME'重复输入...”

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

6 个答案:

答案 0 :(得分:23)

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

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

删除级联,现在只需要决定是否要插入用户角色,就可以解决问题。据我所知,应该有单独的功能。

另请勿使用ArrayList,请使用HashSetArrayList允许重复。

答案 1 :(得分:8)

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

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

确切的问题是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。只需传递一个数组,即: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

什么和为什么

  • cascade = {CascadeType.MERGE}属性已添加到子实体,如JPA Docs
  • 中所述
  

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

    为正确的级联JPA Docs 添加了
  • {{1}}
  

级联EntityManager.merge()操作。如果调用merge()   父母,然后孩子也将合并。这应该是正常的   用于依赖关系。请注意,这只会影响   级联的合并,关系引用本身将永远   合并。

答案 2 :(得分:0)

  

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

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

答案 3 :(得分:0)

重复原因: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

答案 4 :(得分:0)

我遇到了同样的问题,但还无法解决。 我的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表)中使用交付。 不知道现在如何进行。

答案 5 :(得分:0)

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