【问题标题】:How to remove a many-to-many association in Hibernate without actually deleting Objects如何在 Hibernate 中删除多对多关联而不实际删除对象
【发布时间】:2011-10-31 10:21:49
【问题描述】:

我在 Spring 驱动的应用程序中有 2 个 Hibernate 类,如下所示:

@Entity
public class Image {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@ManyToMany(mappedBy = "picture", fetch = FetchType.LAZY)
private Set<InspectionObjectDetail> inspectionObjectDetails = new HashSet<InspectionObjectDetail>();

@Override
public int hashCode() {
    // Eclipse generated hashCode()
}

@Override
public boolean equals(Object obj) {
    // Eclipse generated equals()
 }
}

@Entity
public class InspectionObjectDetail {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;


@ManyToMany (fetch = FetchType.LAZY)
@JoinTable(
name = "MXInspectionObjectDetail_picture",
joinColumns = {@JoinColumn(name = "INSPECTIONOBJECTDETAIL_ID")},
inverseJoinColumns = {@JoinColumn(name = "PICTURE_ID")}
)
@Where(clause = "active = true")
private Set<UOCImage> picture = new HashSet<UOCImage>();
@Override
public int hashCode() {
    // Eclipse generated hashCode()
}

@Override
public boolean equals(Object obj) {
    // Eclipse generated equals()
 }
}

我可以向这个关系添加元素并很好地查询它们,但是我尝试删除关系而不实际删除这些对象之一的 jUnit 测试失败。

我目前正在做的是在事务上下文中取消每个对象与另一个对象的链接:

// Junit 测试

@Test
public void deleteInspectionObjectDetailImage() {
    InspectionObjectDetail inspectionObjectDetail = new InspectionObjectDetail();
    dao.save(inspectionObjectDetail);
    someClass.saveInspectionObjectDetailImage(controlObject.getId());
            //passes
    assertTrue(inspectionObjectDetail.getPictures().size() == 1);
    UOCImage image = inspectionObjectDetail.getPictures().iterator().next();
    someClass.deleteImage(image);
    Set<UOCImage> images = inspectionObjectDetail.getPictures();
            //fails
    assertTrue(images.size() == 0);
}

// BO类

@Transactional
@Component
public class SomeClass() {
    public void delteImage(Image image) {
        if(image!= null) {
        image.setActive(false);
        while(image.getInspectionObjectDetails().iterator().hasNext()) {
             InspectionObjectDetail inspectionObjectDetail = image.getInspectionObjectDetails().iterator().next();
             inspectionObjectDetail.getPictures().remove(image);
             image.getInspectionObjectDetails().remove(inspectionObjectDetail);

                }
        }
        sessionFactory.getCurrentSession().flush();
     }

}

Hibernate 日志告诉我,它对关系的插入已正确执行,但我的删除语句没有出现(即使在 session.flush() 之后)。 如果可能的话,我希望保持惰性获取方式不变,因为我希望任一表上的大量读取都不需要加载相应的其他类。

有人指点我吗?

更新

关于 @Thor84no 的建议,我尝试通过 iterator.remove() 函数删除每个关联,因此我的 BO 对象现在看起来像这样:

// BO类

@Transactional
@Component
public class SomeClass() {
    public void delteImage(Image image) {
        if(image!= null) {
        image.setActive(false);
        Iterator<InspectionObjectDetail> inspectionObjectDetailsIterator = image.getInspectionObjectDetails().iterator();
        while (inspectionObjectDetailsIterator.hasNext()) {
            InspectionObjectDetail inspectionObjectDetail = inspectionObjectDetailsIterator.next();
            Iterator<UOCImage> imageIterator = inspectionObjectDetail.getPictures().iterator();
            while (imageIterator.hasNext()) {
                if (imageIterator.next() == image) {
                    imageIterator.remove();
                }
            }
            inspectionObjectDetailsIterator.remove();
        }
    }
    sessionFactory.getCurrentSession().flush();
}

}

在我的(编辑过的)Junittest 中:

    // ...
    someClass.deleteImage(image.getId());
    assertTrue(image.getInspectionObjectDetails().size() == 0);
    assertTrue(inspectionObjectDetail.getPictures().size() == 0);

第一个断言成功,第二个断言失败,所以之间的双向关联

Image InspectionObjectDetail 仅简化为
Image InspectionObjectDetail

【问题讨论】:

  • someMethodToDelete 是在deleteInspectionObjectDetailImage 中称为deleteImage 的方法,我假设不是直接的,那么它们有什么关系?为什么要在此方法中创建和填充新的 ArrayList,而不是在要从中删除内容的现有 List 上使用迭代器? controlObject 是什么?它是如何填充的?
  • @Thor84no ,感谢您的反馈 - 虽然试图使示例简短易懂,但我把它搞砸了 - (以前命名的) someMethodToDelete 方法是 deleteImage 方法。另外,感谢重复 ArrayList 上的指针,我使用副本来避免并发修改异常,但这是错误的方法。我对其进行了重构以使用迭代器(请参阅编辑),但原来的问题仍然是一样的
  • 好的。这说明了一点。但是,您当前的代码可能会导致您所指的ConcurrentModificationException。使用迭代器时,将其存储为 while 外部的变量(或使用 for 循环)并在迭代器本身上使用 remove() 方法来删​​除使用 next() 返回的最后一个对象。除此之外,在我有机会对其进行测试之前,我不明白为什么它不会被删除。
  • 您在deleteInspectionObjectDetailImage 中的deleteImage 调用传入image.getId(),但您在SomeClass 中的deleteImage 方法将图像作为参数。你是如何获得 Image 实例的?
  • 哎呀,这是简单示例中的另一个错误,而不是实际代码中的错误。很抱歉造成不必要的麻烦,我正在传递SomeClass 中收到的Image 实例。

标签: java hibernate hibernate-mapping


【解决方案1】:

对于任何有类似问题的人来说,真正的问题是inspectionObjectDetail 中的图片集在deleteImage 调用之后仍然包含图像。为了最容易地测试是否是这种情况,您可以使用调试器单步执行代码并检查 Set 是否更改或在调用 remove 方法之前和之后将其打印出来。

如果您发现对象没有被移除,请确保检查您要从 Set 中移除的对象的 hashCode()equals() 方法。 Set 可能只使用hashCode(),但请记住hashCode()equals() 应该是一致的,并且不能保证它们会忽略equals()

【讨论】:

    【解决方案2】:
    while(image.getInspectionObjectDetails().iterator().hasNext()) {
        InspectionObjectDetail inspectionObjectDetail = image.getInspectionObjectDetails().iterator().next();
        inspectionObjectDetail.getPictures().remove(image);
        image.getInspectionObjectDetails().remove(inspectionObjectDetail);
    }
    

    您知道每次调用列表时调用 iterator() 都会创建一个新的迭代器吗?我想您的问题是(或至少其中一个)您在这里处于无限循环中。只要 image.getInspectionObjectDetails() 至少有 1 个元素,while 将始终为真,而且 image.getInspectionObjectDetails().iterator().next() 将始终返回相同的(第一个)元素。

    试试这样的:

    Iterator<InspectionObjectDetail> it = image.getInspectionObjectDetails().iterator();
    while (it.hasNext()
        InspectionObjectDetail detail = it.next();
        detail.getPictures().remove(image);
        it.remove();
    }
    

    更新:

    if (imageIterator.next() == image)
    

    永远不要在 java 中比较这样的对象;改用 equals 并在您的实体上实现 equals/hashCode。 == 将简单地检查两个操作数是否具有相同的引用,因此这可能会产生错误,尽管在逻辑上它是相同的实体(相同的业务密钥):http://leepoint.net/notes-java/data/expressions/22compareobjects.html

    之后你可以重写:

    Iterator<UOCImage> imageIterator = inspectionObjectDetail.getPictures().iterator();
                while (imageIterator.hasNext()) {
                    if (imageIterator.next() == image) {
                        imageIterator.remove();
                    }
                }
    

    作为:

    inspectionObjectDetail.getPictures().remove(image);
    

    【讨论】:

    • 感谢@TheStijn,您对迭代器的看法是正确的!我改变了这一点,但是问题保持不变(至少对于房地产的一侧,另一侧被删除为意图) - 我已经相应地更新了我的问题
    • 你是对的,这是我应该摆脱的可怕的坏习惯!感谢重构提示,这比我的尝试更“干净的代码”,我相应地重构了它
    【解决方案3】:

    删除关联的经验法则是清除涉及的Java对象中的集合,然后保存对象。

    因此,如果A类有一个B类的集合,而B类有一个A类的集合(多对多关系的情况),那么简单地清除该集合

    • A 类中的 B 类
    • B 类中的 A 类

    并保存A类和B类。关系将被删除,但对象将保留。

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-01-13
      • 1970-01-01
      • 2016-05-03
      • 2011-05-06
      • 1970-01-01
      • 1970-01-01
      • 2020-01-23
      • 1970-01-01
      相关资源
      最近更新 更多