我在数据库中有2个表:
每个广告必须属于一个类别,因此在广告表中我定义了一个指向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结合使用。
表定义:
AdCategories
@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;
...
答案 0 :(得分:0)
如果您在AdCategory
的广告集合中声明了REMOVE(或ALL)类型的级联,则告诉JPA:当我在remove()
上致电AdCategory
时,也请致电{ {1}}关于此集合中的所有广告。这就是JPA所做的。
您有双向关联,您有责任确保关联的双方都处于连贯状态。因此,如果您更改广告的类别,则还应该从其类别中的广告集中删除此广告,并且还应将广告添加到其新类别中。在所有情况下并不是绝对强制性的,但在你的情况下,它是。
另外,你的命名非常糟糕。 remove()
的实例是单类别。因此,实体应命名为AdCategories
。 AdCategory
的内容相同,应命名为Ads
。字段Ad
不包含类别ID,而是包含类别。它的名称应为adCategoryId
adCategory
,而不是category
。为什么要命名字段adCategoryId
?这是广告类中的ID,因此它显然已经是广告的ID。因此应将其命名为adId
。 id
应命名为descriptionLong
。这可能看起来像细节,但这些细节使代码看起来很好并且可读。
答案 1 :(得分: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);
}
}
非常粗糙的伪代码,你需要充实它
某个类别是否真的需要维护使用它的所有广告的列表?从概念上讲,我认为不应该这样做。随着时间的推移,列表会变得非常大,您可以随时动态查询它,而不是将其存储在每个类别中。但这是你必须从商业角度做出的决定。