I have these classes.
#Entity
#Table(name ="a")
class A{
private Integer aId;
private B b;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_a")
public Integer getAId() {
return aId;
}
#OneToOne(mappedBy = "a", cascade={CascadeType.ALL})
public B getB() {
return b;
}
}
#Entity
#Table (name= "b")
class B{
private A a;
#OneToOne
#JoinColumn(name = "id_a")
public A getA() {
return a;
}
}
And tables look like:
A) | id_A |....other fields...|
B) | id_B | fk_id_A |..other fields..|
But, when I try to delete an instance of A, I get,
org.hibernate.ObjectDeletedException:
deleted object would be re-saved by
cascade :
(remove deleted object from associations)[B#130]
I've tried setting null on cross references:
b.setA(null)
a.setB(null)
But exception still gets thrown.
All that I have accomplished is to delete a row in A, and leave B's foreign Key null, but when i re try to delete B, get the same error.
This is my delete code:
public static void delete(Object object) throws Exception {
Transaction tx = getSession().beginTransaction();
try {
getSession().delete(object);
tx.commit();
} catch (Exception ex) {
tx.rollback();
getSession().close();
throw ex;
}
}
getSession always returns a valid session.
Is there anything I'm missing?
Start from top and continue working down. You need to delete the reference to A in table B. So locate the reference in B of the record you're trying to delete in A and delete that record in B. Then go back to A and delete the record in A.
If you're using SQL Server, you can set cascading delete on table A and this will be done for you automatically. You have to be careful with this though. I wouldn't recommend this for complex structures.
Are you performing the nulling of cross-references in a separate transaction? Those changes may need to be committed for the delete to work.
Related
How can I pass the test? (was working before migrate my code to use repositories). The bs are stored in the database after save, but the object are not updated. What I have to do to achieve it?
Given these classes:
#Entity
public class A {
#Id
private String id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "aId")
private Set<B> bs= new HashSet<B>();
...
}
#Entity
public class B {
#Id
#GeneratedValue
private int id;
private String aId;
private String foo;
...
}
And Repository:
#RepositoryDefinition(domainClass = A.class, idClass = String.class)
public interface ARepository {
...
void save(A a);
...
}
This test fail:
// "a" saved and flushed
B b = new B();
b.setAId(a.getId());
a.getBs().add(b);
ARepository.save(a);
assertTrue(b.getId() > 0);
repository.save() does persist (if the provided argument is transient) or merge (otherwise).
Since a is not transient, merge is performed, meaning that there is no persist operation that could be cascaded to bs.
You either have to save b explicitly or add b to a new a before the a is saved, so that persist is cascaded properly.
Probably, the reason is that B object is not in persisted state yet. As soon as it will be saved - you shouldn't get errors.
Should look like this:
// "a" saved and flushed
B b = new B();
BRepository.save(b)
b.setAId(a.getId());
a.getBs().add(b);
ARepository.save(a);
assertTrue(b.getId() > 0);
Also could you please provide stacktrace? Would be really helpful.
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!
EclipseLink provides a #CascadeOnDelete annotation that aligns with database "ON DELETE CASCADE" contraint. This annotation tells EclipseLink that the entity will be deleted by the database foriegn key constraint when this entity is deleted, and if using DDL, EclipseLink will generate the table with the proper constraint.
see https://wiki.eclipse.org/EclipseLink/Examples/JPA/DeleteCascade for details.
I think though that you can get by with a simple cascade delete on the FriendshipRelation.person mapping:
#Entity
public class FriendshipRelation {
..
#OneToOne(cascade=CascadeType.REMOVE)
private Person person;
This will force JPA to remove any referenced person when the FriendshipRelation instance is removed.
my problem is that I cannot save my entity because it contains another entity, mapped by a key that is also a part of this table's primary key. The table looks like this:
table C:
+-----+------+
| id_A | id_B |
+-----+------+
..where idA is the primary key of table A with EntityA and idB the primary key of table B with EntityB.
so its basically a n-to-m relation. This is the entity I'm using for table C:
#Entity
public class EntityC {
private long idA;
private EntityB b;
#Id
#Column(name = "id_A")
public long getIdA() {
return idA;
}
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
...setters are here...
}
Please note that id_A is mapped as is (the id), while id_B is mapped as its object representation, EntityB. This is what I want to do with it:
EntityC c = new EntityC();
c.setIdA(123);
c.setB(new EntityB());
em.persist(c);
tx.commit();
em.close();
I want to persist EntityB ONLY IF I can persist EntityC.
on tx.commit() I get this exception: org.hibernate.TransientObjectException: object references an unsaved transient instance
I suppose this happens because part of the primary key, id_B, is not saved. But i set cascading to all so there should be no problem!
Why is this not working?
EDIT:
When I do this:
em.persist(c.getB());
em.persist(c);
it works. But can't Hibernate/JPA do that automatically? I thought that's what cascading is good for.
EDIT2:
added an embeddedId instead of id_A and id_B:
#Embeddable
public class EntityCID implements Serializable {
public long idA;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B", referencedColumnName = "id")
public EntryB b;
}
EntityC now looks like:
#Entity
public class EntityC implements Serializable {
private EntityCID id;
...
#EmbeddedId
public void getId() {
return id;
}
}
but I still get the transient object exception if I don't em.persist(c.getId().b); before em.persist(c). Sticking to that, although it is ugly.
#Trein: it is not bidirectional. EntityB code:
#Entity
public class EntityB implements Serializable {
public long id;
public String text;
}
If you think about it what you are seeing makes perfect sense.
EntityC is is the 'owning side' of the relationship C<>B: it defines the JoinColumn and EntityB has the 'mappedBy' attribute.
So on saving C, order of events would normally be:
insert into C/update C
insert into B/update B
Now in your case this causes issues as obviously C can only be saved if B has been persisted first.
In terms of your statement above: I want to persist "EntityB ONLY IF I can persist EntityC." How can this ever be the case?
JPA has a concept of 'Derived Identifiers', which I am not overly familiar with however is defined in the book Pro JPA as occurring when:
When an identifier in one entity includes a foreign key to another
entity, we call it a derived identifier. Because the entity containing
the derived identifier depends upon another entity for its identity,
we call the first the dependent entity. The entity that it depends
upon is the target of a many-to-one or one-toone relationship from the
dependent entity, and is called the parent entity
Now, despite the original advice that you had two #Id attributes defined and this was wrong it would however appear that having an additional #Id on a 1-2-m is in fact valid in JPA 2 for precisely this case.
The book gives a number of ways of dealing with Derived Identifiers however one example given below looks fairly similar to your case. So you may want to investigate further the #MapsId attribute.
#Entity
public class Project {
#EmbeddedId private ProjectId id;
#MapsId("dept")
#ManyToOne
#JoinColumns({
#JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
#JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")})
private Department department;
// ...
}
#Embeddable
public class ProjectId implements Serializable {
#Column(name="P_NAME")
private String name;
#Embedded
private DeptId dept;
// ...
}
See further:
How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6
Is it a bidirectional relationship? I would suggest you to remove #Id getB() and perform the modifications:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "id_B")
#PrimaryKeyJoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
Your entity class must have only one attribute annotated with #Id. Usually when you need this, you create a class that will store both properties and this will act as a Id Class.
You can not pass new Entity() for reference. Because it won't have any values in it(even primary key). So how can hibernate will insert it as foreign key to the table. And cascade will save your parent object if its not saved,no need to call save method for all. But when you passing new object it won't do.
I have the following classes:
import play.db.ebean.Model;
import javax.persistence.*;
#Entity
public class A extends Model {
#Id
private int id;
/* Other irrelevant properties */
#OneToOne(cascade = CascadeType.ALL, optional = true)
private B b;
}
import play.db.ebean.Model;
import javax.persistence.*;
#Entity
public class B extends Model {
#Id
private int id;
/* Other irrelevant properties */
#OneToOne(mappedBy = "b")
private A a;
#OneToOne(cascade = CascadeType.ALL, optional = false)
private C c;
}
import play.db.ebean.Model;
import javax.persistence.*;
#Entity
public class C extends Model {
#Id
private int id;
/* Other irrelevant properties */
#OneToOne(mappedBy = "c")
private B B;
}
In the database, it looks like this:
Table a
Columns id, ... (other irrelevant columns), b_id
Table b
Columns id, ...(other irrelevant columns), c_id
Table c
Columns id, ...(other irrelevant columns)
Now in one the controller methods (for those familiar with Play) I have an object of A, and would like to delete its property "b" from the database. This should also cascade to c, so c gets deleted too.
This is my current approach:
B b = a.getB();
b.delete();
This throws an exception though:
[PersistenceException: ERROR executing DML bindLog[] error[Cannot
delete or update a parent row: a foreign key constraint fails
(databasename.a, CONSTRAINT fk_a_payme_4 FOREIGN KEY (b_id)
REFERENCES b (id))]]
It basically boils down to the fact that I'm trying to delete b, while a still holds a foreign key reference to b in column b_id, so I should first set that reference to null.
In Java, this translates to:
B b = a.getB();
a.setB(null);
a.update();
b.delete();
This does set the reference to b in object a to null and correctly deletes the b object, but c does not get deleted from the database. (Why? I though the cascade property would take care of this)
The only way I have found to fix this is to explicitly delete c as well, so like this:
B b = a.getB();
C c = b.getC();
b.setC(null);
b.update();
c.delete();
a.setB(null);
a.update();
b.delete();
I'm not very pleased about this though, because that's already 8 lines of code to delete two rows in the database, and it would be more if there were more relationships in this picture.
So as for my question:
How can I delete b from a so the reference from a to b is deleted automatically first and how can I make sure c is deleted too when I delete b?
Thank you in advance!
Edit: best idea so far: move the ownership of the a-b relationship to b.
Try using
class A{
......
#JoinColumn(nullable=true)
private B b;
.......
}
See this question for more info What is the difference between #ManyToOne(optional=false) vs. #Column(nullable=false).
I run into some issue with #johny answer. Try this.
#JoinColumn(name = "b_id", nullable = false)
I have to test some code I have not myself written. It is an integration test: the application is running continuously on a server and my tests run against it.
The tests are Selenium tests, they launch a browser, execute some JavaScript inside it to simulate user actions and checks if the database is correctly updated. I have to restore the database to its initial state after each.
To do this, I use Spring annotations and Hibernate via DAO's I have not myself written.
The problem is that there are circular foreign keys. An object of class A has a OneToMany relationship with objects of type B, and there is also a ManyToOne association with the same class. I try to delete an object of type A and all its associated B's in the same transaction, but it doesn't work because Hibernate tries to set "defaultB" to null before deleting the object of type A. It is completely unnecessary to nullify it, although it makes sense to do it once the referred object of type B is deleted.
I (naively) thought that because the 2 operations were executed in the same transaction, deleting the object "a" of type A referring to (and referenced by) the object "b" of class B and deleting b at the same time would be no problem. However, I was plain wrong. It there is way to do this without changing the DB model (which I haven't written)?
Update 1: I don't understand why, when I execute mySession.delete(B), Hibernate tries to nullify a key it knows as non-nullable...any thoughts about this?
Update 2: there is a one-to-many relationship from class C to class B. Hibernate also tries to nullify the "c_id" field in the table corresponding to B, when I delete the C object that has this c_id. And that even though I delete the object of class B before its "parent". I know Hibernate reorders the queries and adds some stuff of its own, but I don't get the point of reordering queries that are already in the correct order to make them fail.
Here are (relevant parts of) the classes:
#Entity
#Table(name = "A")
public class A {
private Set<B> bs;
private B defaultB;
#OneToMany(mappedBy = "a", fetch = LAZY)
public Set<B> getBs() {
return bs;
}
public void setBs(Set<B> bs) {
this.bs = bs;
}
#ManyToOne(fetch = LAZY, optional = false)
#JoinColumn(name = "default_b_id", nullable = false)
public B getDefaultB(){
return defaultB;
}
public void setDefaultB(B defaultB) {
this.defaultB = defaultB;
}
}
#Entity
#Table(name = "B")
public class B {
private a;
#ManyToOne(fetch = LAZY, optional = false)
#JoinColumn(name = "A_id", nullable = false)
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
I try to delete an object of type A and all its associated B's in the same transaction
You should cascade the REMOVE operation for this if you don't want to have to remove all Bs manually. I would try the following (using cascade on both associations):
#Entity
#Table(name = "A")
public class A {
private Set<B> bs;
private B defaultB;
#OneToMany(mappedBy = "a", fetch = LAZY, cascade=CascadeType.REMOVE)
public Set<B> getBs() {
return bs;
}
public void setBs(Set<B> bs) {
this.bs = bs;
}
#ManyToOne(fetch = LAZY, optional = false, cascade=CascadeType.REMOVE)
#JoinColumn(name = "default_b_id", nullable = false)
public Strategy getDefaultB(){
return defaultB;
}
public void setDefaultB(B defaultB) {
this.defaultB = defaultB;
}
}
I cannot change these annotations. BTW, I do remove all associated B's manually, it's just that the queries Hibernate issues don't do what I want.
Ok... But then my guess is that you're not updating correctly both sides of the bidirectional association before remove the entities. This is typically done in defensive programming methods like this (in A):
public removeFromBs(B b) {
b.setA(null);
this.getBs().remove(b);
}
I assume that you want to delete a, but Hibernate does not allow it because b still refer to it?
Since you meta-model do not specify cascade delete, you need to "break" the link of b to a before deleting a. So do b.setA(null) before deleting a.