ObjectDB ObjectDB

OneToMany and cascade delete problem

#1

Hi,

I don't really know if this is a bug or maybe model that I make is bad. I have a problem with with two different classes with OneToMany relation to one, shared class. For example (first class):


@Entity
public static class Store {


@OneToMany(targetEntity = Offer.class, mappedBy = "store")
  private List<Offer> offers;
}

second class:


@Entity
public static class Product {
  @OneToMany(targetEntity = Offer.class, mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  private Set<Offer> offers;
}

and a shared class Offer:


@Entity
public static class Offer {
  @ManyToOne
  private Product product;
  @ManyToOne
  private Store store;
}

Naming of this class is not perfect, but in my application Offer contains URL of product that I can parse for price. But this is not important here :) The main purpose of this, is to have all offers for products (one Product can have many Offers) and for a given store list of all Offers of all Products that belongs to this Store. This way I can easily iterate over all offers from all products for given store.

The problem is, that after removing product, all "connected" offers are also removed (cascade). After that, when I iterate over offers in store, there is null pointer about not finding offer (which is deleted together with product).

You can see this in attached example. The question is - is it a bug? Or I shouldn't use that kind of relations?

edit
delete
#2

Running the test case (with enhancer enabled) produces output that ends with:

Store: 4
Product: 24, product name: Product 13
Product: 84, product name: Product 73
Product: 64, product name: Product 53
Product: 74, product name: Product 63
Product: 104, product name: Product 93
Product: 34, product name: Product 23
Product: 94, product name: Product 83
Exception in thread "main" [ObjectDB 2.x] javax.persistence.EntityNotFoundException
Entity is not found: com.objectdb.test.bug.forum.T501$Product#54 (error 631)
at com.objectdb.pc.track.EntityTracker.beforeAccess(EntityTracker.java:955)
at com.objectdb.test.bug.forum.T501$Product.__odbGet_id(T501.java:1)
at com.objectdb.test.bug.forum.T501$Product.getId(T501.java:158)
at com.objectdb.test.bug.forum.T501.listStoresProducts(T501.java:87)
at com.objectdb.test.bug.forum.T501.main(T501.java:73)

I think that the Offer class and cascading delete from Product to Offer are irrelevant to the exception.

Let focus only in Store and Product. In the relationship between Store and Product - Store is the owner. Therefore, you must remove a Product from the list of Product instances in a Store when the Product is deleted. Otherwise a broken reference from Store to the deleted Product is formed.

ObjectDB Support
edit
delete
#3

I must be blind lately... Looking at exception I saw Offer, and it's Product. Sorry for misleading.

Anyway - I just modified example (attached to this post). After removing Product, Offer is still in database, but all fields in related Product are nullified. Only ID stay the same. There is no exception for this example, but if you look at logs:

Store: 4, size: 10
Offer: Offer for product: 94, store: 4
Offer: Offer for product: 54, store: 4
Offer: Offer for product: 24, store: 4
Offer: Offer for product: 104, store: 4
Offer: Offer for product: 44, store: 4
Offer: Offer for product: 34, store: 4
Offer: Offer for product: 14, store: 4
Offer: Offer for product: 74, store: 4
Offer: Offer for product: 84, store: 4
Offer: Offer for product: 64, store: 4

bolded part should be gone - I think.

 

edit
delete
#4

Running the new test with enhancement enabled produces:

Store: 4, size: 9
Offer: Offer for product: 64, store: 4
Offer: Offer for product: 24, store: 4
Offer: Offer for product: 14, store: 4
Offer: Offer for product: 104, store: 4
Offer: Offer for product: 34, store: 4
Offer: Offer for product: 74, store: 4
Offer: Offer for product: 44, store: 4
Offer: Offer for product: 94, store: 4
Offer: Offer for product: 84, store: 4

but with no enhancement - produces:

Listing stores / offers:
Exception in thread "main" [ObjectDB 2.x] javax.persistence.PersistenceException
Failed to write the value of field field com.objectdb.test.bug.forum.T501$Store.offers using reflection (error 363)
at com.objectdb.jpa.JpaQuery.getResultList(JpaQuery.java:674)
at com.objectdb.test.bug.forum.T501.listOffersForStores(T501.java:83)
at com.objectdb.test.bug.forum.T501.main(T501.java:72)
Caused by: com.objectdb.error.UserException: Failed to write the value of field field com.objectdb.test.bug.forum.T501$Store.offers using reflection
at com.objectdb.error.ErrorMessage.construct(ErrorMessage.java:74)
at com.objectdb.type.user.UserMember.newException(UserMember.java:868)
at com.objectdb.type.user.UserMember.write(UserMember.java:544)
at com.objectdb.type.user.UserMemberList.write(UserMemberList.java:516)
at com.objectdb.pc.util.EntityHelper.writeForComparison(EntityHelper.java:207)
at com.objectdb.pc.track.EntityTracker.backupEntity(EntityTracker.java:740)
at com.objectdb.pc.load.EntityLoader.initLoadedEntity(EntityLoader.java:480)
at com.objectdb.pc.load.EntityLoader.completeEntityLoad(EntityLoader.java:444)
at com.objectdb.pc.load.EntityLoader.loadEntity(EntityLoader.java:159)
at com.objectdb.pc.ObjectContext.loadEntity(ObjectContext.java:1057)
at com.objectdb.pc.ObjectContext.obtainEntity(ObjectContext.java:960)
at com.objectdb.pc.ObjectContext.findEntityByRef(ObjectContext.java:809)
at com.objectdb.query.result.SimpleResultBuilder.buildResult(SimpleResultBuilder.java:149)
at com.objectdb.pc.query.QueryRunner.prepareResults(QueryRunner.java:523)
at com.objectdb.pc.query.QueryRunner.run(QueryRunner.java:204)
at com.objectdb.jpa.JpaQuery.getResultList(JpaQuery.java:665)
... 2 more
Caused by: com.objectdb.error.jpa._EntityNotFoundException: Entity is not found: com.objectdb.test.bug.forum.T501$Product#54 - field com.objectdb.test.bug.forum.T501$Offer.product
at com.objectdb.error.jpa.JpaErrorManager.buildAPIUserException(JpaErrorManager.java:94)
at com.objectdb.error.ErrorManager.handleInternalException(ErrorManager.java:59)
at com.objectdb.type.inv.InverseProxy.loadCollection(InverseProxy.java:103)
at com.objectdb.type.inv.InverseProxy.beforeAccess(InverseProxy.java:83)
at com.objectdb.type.inv.InverseListProxy.isEmpty(InverseListProxy.java:136)
at com.objectdb.type.misc.TypeWriter.writeCollection(TypeWriter.java:434)
at com.objectdb.type.misc.TypeWriter.writeElement(TypeWriter.java:262)
at com.objectdb.type.user.UserMember$ReflectionMember.write0(UserMember.java:935)
at com.objectdb.type.user.UserMember.write(UserMember.java:541)
... 15 more

It indicates a problem in reflection mode. Please approve that this is also what you get.

ObjectDB Support
edit
delete
#5

Yes, the same exception without enhancement.

edit
delete
#6

The problem is the result updating a bidirectional relationship (Product - Offer) only on one side.

It is the application responsibility to apply updates on both sides. In practice, however, updating only the owner side is usually sufficient because the other side is being updated automatically when the object is retrieved.

But until the object is retrieved - you may have a broken relationship as in this case in reflection mode (with enhancement, weak references are used more intensively, so Product instances are removed from the cache and then retrieved again, fixing the broken relationship).

When using a short term EntityManager for updating the owner side without working with the mapped by side - the mapped by side will be updated when it is later retrieved using another EntityManager. But if the same EntityManager is used for operations on both sides as in this test case - updating only the owner side can cause this strange behavior.

Notice that this can be fixed by forcing retrieval of the mapped by side, using refresh:

    em.getTransaction().begin();
    Product p = em.find(Product.class, 54);
    em.refresh(p); // <= fixes incomplete bidirectional relationships
    em.remove(p);
    em.getTransaction().commit();

ObjectDB Support
edit
delete

Reply

To post on this website please sign in.