I have been exploring the Datanucleus framework and was trying some of the examples provided in the documentation. I got stuck in the M-N relation example using Sets. When I try to remove an object from the Set it is removed from the Set, but once I persist the container objects, the entry in the join table is not removed. This results in my container objects still holding the removed object.
I have a unit test that exposes the issue:
@Test
public void testMNRelation() {
final Product product = new Product();
product.setName("Product 1");
product.setPrice(100);
final Product product2 = new Product();
product2.setName("Product 2");
product2.setPrice(130);
final Supplier supplier = new Supplier();
supplier.setName("Supplier 1");
Set<Supplier> suppliers = product.getSuppliers();
suppliers.add(supplier);
Set<Product> products = supplier.getProducts();
products.add(product2);
final PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory("MyStore");
PersistenceManager pm = pmf.getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(product);
pm.makePersistent(product2);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
pm.close();
pm = pmf.getPersistenceManager();
tx = pm.currentTransaction();
try {
tx.begin();
System.out.println();
System.out.println("Fetch from store (before removal)");
Supplier s = pm.getObjectById(Supplier.class, supplier.getId());
System.out.println("supplier name: " + s.getName());
System.out.println("# products: " + s.getProducts().size());
Product p = pm.getObjectById(Product.class, product.getId());
System.out.println("product name: " + p.getName());
System.out.println("# suppliers: " + p.getSuppliers().size());
Product p2 = pm.getObjectById(Product.class, product2.getId());
System.out.println("product name: " + p2.getName());
System.out.println("# suppliers: " + p2.getSuppliers().size());
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
pm.close();
Set<Product> updatedProducts = supplier.getProducts();
updatedProducts.remove(product);
Set<Supplier> updatedSuppliers = product.getSuppliers();
updatedSuppliers.remove(supplier);
System.out.println();
System.out.println("Before persist (after removal)");
System.out.println("supplier name: " + supplier.getName());
System.out.println("# products: " + supplier.getProducts().size());
System.out.println("product name: " + product.getName());
System.out.println("# suppliers: " + product.getSuppliers().size());
System.out.println("product name: " + product2.getName());
System.out.println("# suppliers: " + product2.getSuppliers().size());
pm = pmf.getPersistenceManager();
tx = pm.currentTransaction();
try {
tx.begin();
pm.makePersistent(supplier);
pm.makePersistent(product);
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
pm.close();
System.out.println();
System.out.println("After persist");
System.out.println("supplier name: " + supplier.getName());
System.out.println("# products: " + supplier.getProducts().size());
System.out.println("product name: " + product.getName());
System.out.println("# suppliers: " + product.getSuppliers().size());
System.out.println("product name: " + product2.getName());
System.out.println("# suppliers: " + product2.getSuppliers().size());
pm = pmf.getPersistenceManager();
tx = pm.currentTransaction();
try {
tx.begin();
System.out.println();
System.out.println("Fetch from store");
Supplier s = pm.getObjectById(Supplier.class, supplier.getId());
System.out.println("supplier name: " + s.getName());
System.out.println("# products: " + s.getProducts().size());
Product p = pm.getObjectById(Product.class, product.getId());
System.out.println("product name: " + p.getName());
System.out.println("# suppliers: " + p.getSuppliers().size());
Product p2 = pm.getObjectById(Product.class, product2.getId());
System.out.println("product name: " + p2.getName());
System.out.println("# suppliers: " + p2.getSuppliers().size());
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
pm.close();
}
And the 2 types used in the example from http://www.datanucleus.org/products/datanucleus/jdo/orm/many_to_many.html
@PersistenceCapable(detachable="true")
public class Supplier {
@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.INCREMENT)
private long id;
@Persistent
private String name;
@Persistent(mappedBy="suppliers")
private Set<Product> products = new HashSet<Product>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Product> getProducts() {
return products;
}
public void setProducts(Set<Product> products) {
this.products = products;
}
@Override
public boolean equals(Object o) {
if(this.getId() == 0) {
return false;
}
if(o instanceof Supplier) {
Supplier other = (Supplier) o;
return this.getId() == other.getId();
}
return false;
}
@Override
public int hashCode() {
return Long.toString(this.getId()).hashCode();
}
}
And
@PersistenceCapable(detachable="true")
public class Product {
@PrimaryKey
@Persistent(valueStrategy=IdGeneratorStrategy.INCREMENT)
private long id;
@Persistent
private String name;
@Persistent
private double price;
@Persistent(table="PRODUCTS_SUPPLIERS")
@Join(column="PRODUCT_ID")
@Element(column="SUPPLIER_ID")
private Set<Supplier> suppliers = new HashSet<Supplier>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Set<Supplier> getSuppliers() {
return suppliers;
}
public void setSuppliers(Set<Supplier> suppliers) {
this.suppliers = suppliers;
}
@Override
public boolean equals(Object o) {
if(getId() == 0) {
return false;
}
if(o instanceof Product) {
Product other = (Product) o;
return this.getId() == other.getId();
}
return false;
}
@Override
public int hashCode() {
return Long.toString(this.getId()).hashCode();
}
}
Now, in the console I get
Fetch from store (before removal)
supplier name: Supplier 1
# products: 2
product name: Product 1
# suppliers: 1
product name: Product 2
# suppliers: 1
Before persist (after removal)
supplier name: Supplier 1
# products: 1
product name: Product 1
# suppliers: 0
product name: Product 2
# suppliers: 1
After persist
supplier name: Supplier 1
# products: 1
product name: Product 1
# suppliers: 1
product name: Product 2
# suppliers: 1
Fetch from store
supplier name: Supplier 1
# products: 1
product name: Product 1
# suppliers: 1
product name: Product 2
# suppliers: 1
I would expect something like
Fetch from store (before removal)
supplier name: Supplier 1
# products: 2
product name: Product 1
# suppliers: 1
product name: Product 2
# suppliers: 1
Before persist (after removal)
supplier name: Supplier 1
# products: 1
product name: Product 1
# suppliers: 0
product name: Product 2
# suppliers: 1
After persist
supplier name: Supplier 1
# products: 1
product name: Product 1
# suppliers: 0
product name: Product 2
# suppliers: 1
Fetch from store
supplier name: Supplier 1
# products: 1
product name: Product 1
# suppliers: 0
product name: Product 2
# suppliers: 1
My persistence.xml includes:
<property name="datanucleus.DetachAllOnCommit" value="true" />
<property name="datanucleus.attachSameDatastore" value="true" />
<property name="datanucleus.CopyOnAttach" value="false" />
<property name="datanucleus.cache.collections.lazy" value="true" />
<property name="datanucleus.manageRelationships" value="true" />
<property name="datanucleus.manageRelationshipsChecks" value="true" />
Any idea on how to solve this?
I've got a solution to the issue. The issue seems to relate to my persistence settings. I changed the settings to
as I now handle both sides of the relation in my implementation.
Furthermore I implemented a Factory to simplify my test
So the test is simply expressed as
Which result in the following output