0

我在数据库中有 2 个表:

  • ads - 代表用户定义的广告
  • ad_categories - 表示广告的类别

每个广告必须完全属于一个类别,因此在 ads 表中我定义了一个指向 ad_categories 的外键,其中 ON UPDATE NO ACTION ON DELETE NO ACTION。

在我的应用程序中,用户必须能够删除任何类别,但如果该类别包含广告,则必须在删除类别之前将它们移动到另一个类别。

em.getTransaction().begin();
// get currentNode
AdCategories currentNode = em.find(AdCategories.class, currentNodeId);
// get ads
List<Ads> resultList = em.createQuery("SELECT a from Ads a WHERE a.adCategoryId = :categoryId").setParameter("categoryId", currentNode).getResultList();
// get their new location
AdCategories newLocation = em.find(AdCategories.class, newLocationId);
// set their new location
for(Ads a: resultList)
    a.setAdCategoryId(newLocation);
em.remove(currentNode);
em.getTransaction().commit();

我预计,受影响的广告将更改 ad_category_id,然后将删除空类别。但受影响的广告也被删除了!!

我在 EclipseLink 中启用了 FINEST 级别的日志记录,发现当事务提交时,首先将 UPDATE 查询发送到数据库,这会更改受影响广告的 ad_category_id,然后删除类别,但删除是级联广告!我不明白为什么,因为广告应该在删除之前更新 ad_category_ids 。

我知道,一种简单的解决方法是在删除类别之前调用em.flush(),但我认为这不是最佳解决方案。我想,我需要了解这种行为。

我将 EclipseLink 与 NetBeans 和 PostgreSQL 一起使用。

表定义:

广告类别

@Entity
@Table(name = "ad_categories")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "AdCategories.findAll", query = "SELECT a FROM AdCategories a"),
@NamedQuery(name = "AdCategories.findById", query = "SELECT a FROM AdCategories a WHERE a.id = :id"),
@NamedQuery(name = "AdCategories.findByParentId", query = "SELECT a FROM AdCategories a WHERE a.parentId = :parentId"),
@NamedQuery(name = "AdCategories.findByCategoryOrder", query = "SELECT a FROM AdCategories a WHERE a.categoryOrder = :categoryOrder"),
@NamedQuery(name = "AdCategories.findByCategoryDepth", query = "SELECT a FROM AdCategories a WHERE a.categoryDepth = :categoryDepth"),
@NamedQuery(name = "AdCategories.findByName", query = "SELECT a FROM AdCategories a WHERE a.name = :name"),
@NamedQuery(name = "AdCategories.findByGrandParentId", query = "SELECT a FROM AdCategories a WHERE a.grandParentId = :grandParentId"),
@NamedQuery(name = "AdCategories.findByParentName", query = "SELECT a FROM AdCategories a WHERE a.parentName = :parentName"),
@NamedQuery(name = "AdCategories.findByGrandParentName", query = "SELECT a FROM AdCategories a WHERE a.grandParentName = :grandParentName")})
public class AdCategories implements Serializable {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "adCategoryId")
private Collection<Ads> adsCollection;
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "id")
private Integer id;
@Basic(optional = false)
@Column(name = "parent_id")
private int parentId;
@Basic(optional = false)
@Column(name = "category_order")
private short categoryOrder;
@Basic(optional = false)
@Column(name = "category_depth")
private short categoryDepth;
@Basic(optional = false)
@Column(name = "name")
private String name;
@Column(name = "grand_parent_id")
private Integer grandParentId;
@Column(name = "parent_name")
private String parentName;
@Column(name = "grand_parent_name")
private String grandParentName;
...

广告

@Entity
@Table(name = "ads")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "Ads.findAll", query = "SELECT a FROM Ads a"),
@NamedQuery(name = "Ads.findByAdId", query = "SELECT a FROM Ads a WHERE a.adId = :adId"),
@NamedQuery(name = "Ads.findByName", query = "SELECT a FROM Ads a WHERE a.name = :name"),
@NamedQuery(name = "Ads.findByDescriptionShort", query = "SELECT a FROM Ads a WHERE a.descriptionShort = :descriptionShort"),
@NamedQuery(name = "Ads.findByDescriptionLong", query = "SELECT a FROM Ads a WHERE a.descriptionLong = :descriptionLong")})
public class Ads implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "ad_id")
private Integer adId;
@Basic(optional = false)
@Column(name = "name")
private String name;
@Basic(optional = false)
@Column(name = "description_short")
private String descriptionShort;
@Basic(optional = false)
@Column(name = "description_long")
private String descriptionLong;
@JoinColumn(name = "ad_category_id", referencedColumnName = "id")
@ManyToOne(optional = false)
private AdCategories adCategoryId;
...
4

2 回答 2

0

如果您在 中的广告集合上声明类型为 REMOVE(或 ALL)的级联AdCategory,您会告诉 JPA:当我调用remove()AdCategory,也会调用remove()此集合中的所有广告。这就是 JPA 所做的。

您有一个双向关联,您有责任确保关联的双方处于一致状态。因此,如果您更改广告的类别,您还应该将该广告从其类别中的广告集中删除,并且您还应该将该广告添加到其新类别中。在所有情况下都不是绝对强制性的,但在你的情况下,它是。

另外,你的命名真的很糟糕。的实例AdCategories单个类别。所以实体应该命名为AdCategory. 同为Ads,应命名为Ad。该字段adCategoryId不包含类别 ID,而是包含类别。它应该被命名adCategorycategory而不是adCategoryId。为什么命名该字段adId?它是类 Ad 中的 ID,所以它显然已经是一个 Ad 的 ID。因此它应该被命名为iddescriptionLong应该命名为longDescription. 这可能看起来像细节,但这些细节使代码看起来很好并且可读。

于 2013-03-03T13:00:21.533 回答
0

这里的问题是您定义了一个双向关系,需要手动管理(JPA 提供者不会为您做)。在您的调用代码中,您从广告的角度断开了广告与其类别之间的联系。

for(Ads a: resultList)
    a.setAdCategoryId(newLocation);

但是,您的类别仍然保留一组它认为与其相关的广告,当您删除它时,这些广告也会被删除(因为 CascadeType.ALL 注释)。有两种方法可以解决此问题。

保持双向关系

如果你真的需要,你可以离开双向关系,但是当你想打破它时,你必须适当地解除双方的关系。完全从“拥有”方面管理关系是正常的,所以我会做这样的事情:

public class Ads implements Serializable {
    public void setAdCategoryId(AdCategories category) {
        this.category.removeAd(this);
        this.category = category;
        this.category.addAd(this);
    }
}

非常粗糙的伪代码,你需要充实它

删除双向关系

一个类别真的需要维护所有使用它的广告的列表吗?从概念上讲,我认为不应该。随着时间的推移,该列表会变得非常大,您始终可以动态查询它,而不是将其存储在每个类别中。但这是您必须从业务角度做出的决定。

于 2013-03-03T13:00:37.353 回答