【问题标题】:jpa: when merging many to many previous record gets deletedjpa:合并多对多以前的记录时被删除
【发布时间】:2013-10-17 20:16:30
【问题描述】:

我有一个用户和标签表,还有一个包含多对多关系的 user_tag_xref 表。现在 netbeans 为我生成实体类(使用 eclipselink)下面是实体映射关系

关于用户类

@ManyToMany(mappedBy = "usersList")
    private List<Tags> tagsList;

关于标签类

@JoinTable(name = "USERS_TAG_XREF", joinColumns = {
        @JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID")}, inverseJoinColumns = {
        @JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID")})
    @ManyToMany
    private List<Users> usersList;

现在我的业务逻辑是 RESTfull 服务,一个 json 客户端使用这个方法

 @POST
    @Path("/registration2/tag") 
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
    public Response registration2_3(List<Tags>tagList,@Context HttpServletRequest req){

         Profile p =(Profile) registerMap.get(req.getSession().getId());
     Users u = em.find(Users.class,p.getUserId());


    for(Tags t : tagList){
        t.getUsersList().add(u);
        u.getTagsList().add(t); 
        em.merge(t);
        em.merge(u); 

    }     
   logger.log(Level.INFO, "the taglist created for user");

       return Response.ok(u,MediaType.APPLICATION_JSON).build(); 
    }

问题是每次我合并一个新用户以创建多对多关系时,如果现有 userid=6201 有一个带有 2,3,4 的标签,并且新用户尝试使用标签 id 2, 3,4, 现有用户被删除,新用户合并到标签。我已经阅读了几篇关于在我的实体类中覆盖哈希和等于方法的文章,这些方法在 eclipselink 中默认被覆盖,我也无法将我的集合类型更改为 Set 或 Collection,因为 List 类型非常适合 json 数组。我现在很难过,已经24小时了,会不会是默认映射策略错误?我需要级联吗?

【问题讨论】:

  • 如果您正在查看,有人可以帮忙吗?请
  • 当你对它调用合并时,t.getUserList() 中有什么? t 和 tagList 来自哪里?如前所述,当您调用merge 时,您必须小心标签实际上反映了您想要的数据库中的内容。如果它只是部分完成,您正在用部分数据覆盖完整数据。另外,什么被删除了,只是连接表中的引用吗?
  • 克里斯的好问题。连接表中的引用被删除了吗?
  • t.getUserList() 包含(一个用户)一个 user_id 6201,他有 3 个 tags_id 1,2,3 在一对多关系中,t 和 tagList 来自一个 json 数组,我可以读取数组非常好,但是使用 shailendra 先生编写的代码,如果新的 user_id 6202 使用 post 方法发送 tagList 的 tag_id 为 1,2,3,则 user_id 6202 将有 1,2,3 从而从数据库中删除用户 6201

标签: rest jpa ejb many-to-many eclipselink


【解决方案1】:

在使用合并时您必须格外小心,因为它的语义(如here 所解释)与仅更新有点不同。 由于您的关系是双向的,而用户是相反的一面,所以所有的关系持久性都将由 标签方面。假设您的标签列表包含分离的标签,这意味着所有标签都设置了它们的 id,那么您需要遍历 tagList

Tag managedTag = em.merge(t);

这需要注意,如果 t 是新实例(非托管),则将返回它的持久表示 必须在实例设置其 id 之后或如果实例设置了它们的 id 后使用它,那么 ORM 将使用来自数据库的数据(或者如果存在,则来自第一/第二级缓存)创建一个托管实例。返回的实例是托管的实例

for(Tags t : tagList){
        Tag managedTag = em.merge(t);
        managedTag.getUsersList().add(u);
        u.getTagsList().add(t);         
       User managedUser =  em.merge(u); 

    } 

您还可以在 Tag 端设置 Merge cascade 选项,以节省您的第二次合并调用,并让 ORM 自动管理关系。

【讨论】:

  • 我试过你的代码,它仍然给我同样的结果,我修改了你的代码,认为托管标签可能需要添加到 u.getTagList().add(managedTag);
  • 将 em.merge(t) 替换为 em.find(Tags.class, t.getId()) 看看是否能解决问题。
  • whooaaaaa,我将其替换为 em.find(Tags.class,tgetId()) 并且有效,请问为什么有效?不是从标签中合并的 managedTag = em.merge(t);实际上没有将其合并到托管状态?
  • 我尝试通过询问您的标签来自何处来指出问题所在。如果它们不是完整的实体,则您正在将这些不完整的数据合并到完整的托管实体之上。当应用程序尝试使用存根并仅设置 ID 而不是 em.find/getReference 来获取实际数据时,这是一个问题。合并将使用未填充的空字段覆盖实体数据。使用 find 是有效的,因为您随后只使用您想要的更改来修改整个实体,而不是使用存根中的任何内容。
  • 正如 Chris 所指出的,需要谨慎使用合并,因为在大多数情况下,通常从前端收到的分离实体在关系方面并不完整,合并会考虑这些关系。因此,在大多数情况下,人们倾向于自己合并数据,而不是依赖 JPA 合并(DIY 模式)。我已经阅读了一些建议不要使用合并的书籍(例如,seam in action)。成语是这样的。您首先在持久上下文中加载实体,然后通过手动复制来更新其状态。关系类似。
【解决方案2】:

以下是合并与分离实体和关系的行为方式。

@Entity
public class Tag {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int        id;

    private String     name;

    @ManyToMany(cascade=CascadeType.ALL)
    private List<User> users = new ArrayList();
.......................
}


@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int       id;

    private String    name;
    @ManyToMany(mappedBy="users",cascade=CascadeType.ALL)
    private List<Tag> tags = new ArrayList();
........................
}

测试程序

EntityManagerFactory emf = Persistence.createEntityManagerFactory("Test");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

User user = new User();
user.setName("User");
User managedUser = em.merge(user);
int userId = managedUser.getId();
//create a tag        
Tag tag = new Tag();
tag.setName("Tag");
tag.addUser(managedUser);
Tag managedTag = em.merge(tag);
//save the id locally
int savedId = managedTag.getId();
//managed tag was sent to UI where its name will be changed to "Changed Tag"
em.getTransaction().commit();
em.close();

//create another transaction
EntityManager em1 = emf.createEntityManager();
em1.getTransaction().begin();

//simulate a tag sent form UI 
Tag tagFromUI = new Tag();
tagFromUI.setId(savedId);
tagFromUI.setName("Changed Tag");

// I want to associate a new user to this tag
// so create a new user
User newUser = new User();        
newUser.setName("newUser");       
tagFromUI.addUser(newUser);
Tag managedTagFromUI = em1.merge(tagFromUI);


em1.getTransaction().commit();
em1.close();
emf.close();

这里是生成的SQL和对应的解释

//First transaction begins
insert into User (name) values ('User');
insert into Tag (name) values ('Tag');
insert into Tag_User (tags_id, users_id) values (1, 1);
//First transaction ends
//Second transaction begins
// Since a detached tag is merged, hibernate queries for tag id 1 to load it in persistent context
//This tag is associated with a user
select tag0_.id as id1_3_1_, tag0_.name as name2_3_1_, users1_.tags_id as tags_id1_3_3_, user2_.id as users_id2_4_3_, user2_.id as id1_5_0_, user2_.name as name2_5_0_ from Tag tag0_ left outer join Tag_User users1_ on tag0_.id=users1_.tags_id left outer join User user2_ on users1_.users_id=user2_.id where tag0_.id=1;
//copies the state of detached tag from UI to the managed tag and sends update
update Tag set name='Changed Tag' where id=1;
//since merge is cascaded, hibernate looks for the user list of supplied tag and sees an transient User
// the transient instance is merged (created new in  database as it is not yet persisted)
insert into User (name) values ('newUser');
// merge is called on this new managed instance and the resulted instance is set in the managed Tag instance automatically 
//but for this the old relation has to be broken
delete from Tag_User where tags_id=1;
// and the new relation has to be added in database
insert into Tag_User (tags_id, users_id) values (1, 2);
//second transaction ends

【讨论】:

  • 我的标签类使用基本而用户类使用 GeneratedValue,所以标签值是固定的,我没有使用 jsf 客户端或 java gui 客户端的多对多问题,但是自从我开始工作就像前端人员做 angular js 并发送 json 对象一样,我完全感觉自己是个新手。令人沮丧。我的合并/发布操作不断删除相关记录的卷
  • 如何在spring boot、data rest和jpa应用程序中实现这种行为我的问题在这里stackoverflow.com/questions/43111692/…
【解决方案3】:

在添加新条目时,您需要验证映射不重复或已存在于数据库中。为了更清楚地了解解决方案,请查看链接中提供的我的灵魂

Relationship table data overwritten in many-to-many hibernate

【讨论】:

    猜你喜欢
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    • 2012-05-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多