简体   繁体   English

JPA-在一项交易中删除之前更新不起作用

[英]JPA - update before remove in one transaction not working

I have 2 tables in database: 我在数据库中有2个表:

  • ads - represent user defined advertisements 广告-代表用户定义的广告
  • ad_categories - represent categories for advertisements ad_categories-代表广告的类别

every advertisement must belong to exactly one category, so in ads table I defined a foreign key pointing to ad_categories with ON UPDATE NO ACTION ON DELETE NO ACTION. 每个广告都必须完全属于一个类别,因此在ads表中,我定义了一个指向ad_categories的外键,并带有ON UPDATE NO ACTION ON DELETE NO ACTION。

In my application, user must be able to delete any category, but if that category contains advertisements, they must be moved to another category before category is deleted. 在我的应用程序中,用户必须能够删除任何类别,但是如果该类别包含广告,则必须在删除类别之前将它们移动到另一个类别。

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();

I expected, that affected advertisements will have ad_category_id changed and then the empty category will be removed. 我希望受影响的广告将更改ad_category_id,然后将删除空类别。 But affected advertisements are deleted too!! 但是受影响的广告也会被删除!

I enabled logging in EclipseLink to FINEST level and found out, that when transaction is commited, firstly, UPDATE query is sent to database, which changes ad_category_id for affected advertisements and then category is deleted, but delete is cascaed to advertisements! 我启用了EclipseLink到FINEST级别的登录功能,发现在提交事务时,首先,将UPDATE查询发送到数据库,该数据库将更改受影响广告的ad_category_id,然后删除类别,但删除是级联的! I dont understand why, because advertisements should have updated ad_category_ids before remove occours. 我不明白为什么,因为广告应该在删除广告之前先更新ad_category_ids。

I know, one simple workaround is to call em.flush() before removing the category, but I dont think it is optimal solution. 我知道,一种简单的解决方法是在删除类别之前调用em.flush() ,但我认为这不是最佳解决方案。 I think, I need to understand this behaviour. 我认为,我需要了解这种行为。

I am using EclipseLink with NetBeans and PostgreSQL. 我正在将EclipseLink与NetBeans和PostgreSQL一起使用。

Table definitions: 表定义:

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;
...

Ads 广告

@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;
...

If you declare a cascade of type REMOVE (or ALL) on the collection of ads in AdCategory , you tell JPA: when I call remove() on an AdCategory , also call remove() on all the ads in this collection. 如果您在AdCategory的广告集合上声明类型为REMOVE(或ALL)的AdCategory ,则告诉JPA:当我在AdCategory上调用remove()时,也对该集合中的所有广告都调用remove() So that's what JPA does. 这就是JPA所做的。

You have a bidirectional association, it's your responsibility to make sure both sides of the association are in a coherent state. 您有双向关联,因此有责任确保关联双方处于一致状态。 So if you change the category of an ad, you should also remove this ad from the set of ads in its category, and you should also add the ad to its new category. 因此,如果您更改广告的类别,则还应该将该广告从其类别的广告集中删除,并且还应该将广告添加到新的类别中。 It's not absolutely mandatory in all the cases, but in yours, it is. 在所有情况下都不是绝对强制性的,但在您自己的情况下是强制性的。

Also, your naming is really bad. 另外,您的命名确实很糟糕。 An instance of AdCategories is a single category. 实例AdCategories是一个单一类别。 So the entity should be named AdCategory . 因此,该实体应命名为AdCategory Same for Ads , which should be named Ad . Ads相同,应命名为Ad The field adCategoryId doesn't contain a category ID, but a category. adCategoryId字段不包含类别ID,而是一个类别。 It should be named adCategory of category and not adCategoryId . 它应该被命名为adCategorycategory ,而不是adCategoryId Why name the field adId ? 为什么命名字段adId It's the ID in the class Ad, so it's already obviously the ID of an Ad. 这是广告类中的ID,因此显然已经是广告的ID。 It should thus be named id . 因此,应将其命名为id descriptionLong should be named longDescription . descriptionLong应该命名为longDescription That might seem like details, but those are the details that make code look good and be readable. 这看起来像是细节,但是这些细节使代码看起来不错并且易于阅读。

The problem here is that you defined a bi-directional relationship, which needs to be manually managed (The JPA provider will not do it for you). 这里的问题是您定义了双向关系,需要手动管理(JPA提供程序不会为您完成)。 In your calling code, you break the link between ads and their category, from the point of view of the ads. 从广告的角度来看,在调用代码中,您断开了广告及其类别之间的链接。

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

But, your category is still holding on to a collection of ads that it believes its related too, and when you delete it, those ads get deleted as well (because of the CascadeType.ALL annotation). 但是,您的类别仍然保留着它也认为与其相关的广告集合,并且当您将其删除时,这些广告也会被删除(因为有CascadeType.ALL注释)。 There are two ways you can go about fixing this. 有两种方法可以解决此问题。

Keep the bidirectional relationship 保持双向关系

If you really need to, you can leave the relationship bidirectional, but then you would have to properly disassociate the relationship on both sides, when you want to break it. 如果确实需要,则可以保持双向关系,但是当您想破坏它时,就必须正确地解除双方的关系。 It's normal to manage the relationship entirely from the 'owning' side, so I would do something like this: 完全从“拥有”方面管理关系是正常的,所以我会做这样的事情:

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

Very rough pseudocode, you will need to flesh it out 非常粗糙的伪代码,您需要对其进行充实

Remove the birectional relationship 删除双向关系

Does a category really need to maintain a list of all ads that use it? 类别是否真的需要维护所有使用该类别的广告的列表? Conceptually, I don't think it should. 从概念上讲,我认为不应该。 The list will get very large over time, and you could always query for it dynamically instead of storing it with each category. 随着时间的流逝,该列表将变得非常大,您始终可以动态查询它,而不必将其与每个类别一起存储。 But that's a decision you have to make from a business point of view. 但这是您必须从业务角度做出的决定。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM