My question is about cascading deletes with JPA and Eclipselink.
I would like to model a simple relationship between two entities: A and B. B references A through a property ref2a (in DB terms B.ref2a is connected to A.id through a foreign key with "ON DELETE CASCADE"). My goal is when an A object is deleted to cascade the delete to all B objects that reference it.
I searched a lot, but I cannot make it work. Most solutions I have found are for the opposite situation: A contains a collection of references to B. This works like a charm. But if the reference is on the B side, I don't know how to do it.
Here is the Code sample:
@Entity
public class A
{
@Id
@GeneratedValue
private Integer id;
private String name;
// ...
}
@Entity
public class B
{
@Id
@GeneratedValue
private Integer id;
private String name;
@OneToOne
@JoinColumn(
foreignKey=@ForeignKey(
foreignKeyDefinition="FOREIGN KEY ref2a REFERENCES A id ON DELETE CASCADE"
)
)
private A ref2a;
// ...
}
And the test code:
public class CascadeTest extends TestCase
{
private EntityManagerFactory emf;
private EntityManager em;
@Override
protected void setUp() throws Exception {
emf = Persistence.createEntityManagerFactory("myDB");
em = emf.createEntityManager();
}
@Override
protected void tearDown() throws Exception {
em.close();
emf.close();
}
public void testApp()
{
Integer aid = -1, bid = -1;
try {
em.getTransaction().begin();
A a = new A();
a.setName("My name is A");
B b = new B();
b.setRef2a(a);
b.setName("My name is B, please delete me when A is gone.");
em.persist(a);
em.persist(b);
em.getTransaction().commit();
aid = a.getId();
bid = b.getId();
} finally {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
}
try {
em.getTransaction().begin();
B b = em.find(B.class, bid);
assertNotNull(b);
assertEquals("My name is B, please delete me when A is gone.", b.getName());
assertEquals("My name is A", b.getRef2a().getName());
assertEquals(aid, b.getRef2a().getId());
A a = em.find(A.class, aid);
assertEquals("My name is A", a.getName());
em.remove(a);
em.getTransaction().commit();
em.getTransaction().begin();
// a should have been removed.
// This passes OK.
a = em.find(A.class, aid);
assertNull(a);
// Cascading deletes should have deleted also b.
b = em.find(B.class, bid);
// PROBLEM: This fails - b is still here.
assertNull(b);
em.getTransaction().commit();
} finally {
if (em.getTransaction().isActive())
em.getTransaction().rollback();
}
}
}
I have solved my problem. Really really simple - my initial code was almost right. I just had a syntax problem in the foreign key cascade. The attributes needed to be in brackets "()", I had overlooked that in the documentation. So the change I needed to do is:
@OneToOne
@JoinColumn(
foreignKey=@ForeignKey(
foreignKeyDefinition="FOREIGN KEY (ref2a) REFERENCES A (id) ON DELETE CASCADE"
)
)
private A ref2a;
Please notice the brackets around the two attributes.
This works, deleting an A object also cascades its linked B objects.
Thanks to everybody for your help!