I'm a hibernate newbie and I'm not entirely sure how to get the cascade behavior I'm looking for.
Let's say I have two classes A and B with bi-directional many-to-many mappings to each other. A is the owner side and B is the inverse side (I hope I have the terminology correct).
public class A
{
private Set<B> bSet = new HashSet<B>();
#ManyToMany(targetEntity=B.class, cascade=CascadeType.?)
public Set<B> getBSet()
{
return bSet;
}
}
public class B
{
private Set<A> aSet = new HashSet<A>();
#ManyToMany(mappedBy="bSet", targetEntity=A.class, cascade=CascadeType.?)
public Set<B> getBSet()
{
return bSet;
}
}
I want to select the correct cascade type so that when I delete an A object, any B object containing it is updated and vice-versa.
Which cascade type(s) do I need here?
CascadeType.ALL so every operation affects the other class. See link text
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 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 two entities with a onetoone relationship, A and B. B entity is optional, can be updated and removed on it's own, but must always be linked to an instance of A.
So i have two JPA entities, A and B with a bi-directional relationship. THis is the one from A to B.
#OneToOne(mappedBy = "a", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
I can create a A and B, remove the A and both get deleted. good.
But because of the cascade from A to B, if i em.remove(b) the delete doesn't get persisted. Even if i do a.setB(null) first.
The only way to delete the optional entity, while keeping the cascade, seems to be to use a new JPA2 feature, orphanRemoval=true. Call a.setB(null), then persist A.
This means i can't do operations directly on B, it implies too strong of a composition relationship, all actions on B must be done via A.
But B is not an embedded object, it's a full blown Entity in it's own right, how can i delete it independently of A?
The best way seems to be to remove the cascade, and force users to make sure they delete any related objects separately before they delete the A? Enforced by a FK constraint in the B table.
This is such a straight forward case. two related entities, the relationship is optional on one end, and mandatory on the other.
Oh, this is with hibernate 4.2.3-Final
Your current object design implicitly defined that one is more important than another. That is, one will have the foreign key to another.
To make them equal, you just define the JoinTable between them. Set cascade on both sides, and then everything will work as expected.
Example:
Document class
#Entity
public class Document extends ABaseEntity {
private Medicine medicine;
#OneToOne(cascade = CascadeType.REMOVE)
#JoinTable(
name = "Document_Medicine",
joinColumns =
#JoinColumn(name = "DOC_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "MED_ID", referencedColumnName = "ID"))
public Medicine getMedicine() {
return medicine;
}
public void setMedicine(Medicine medicine) {
this.medicine = medicine;
}
}
Medicine class
#Entity
public class Medicine extends ABaseEntity{
private Document document;
#OneToOne(mappedBy = "medicine", cascade = CascadeType.REMOVE)
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
}
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.
The following doesn't work:
#Entity
class Owner {
#OneToMany(mappedBy="owner", cascade = {CascadeType.ALL})
protected Set<B> getBSet() {
..
}
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
class A {
#ManyToOne
public Owner getOwner() {
...
}
}
#Entity
class B extends A {
}
It causes an exception as such:
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: B.user in Owner.
I am trying to avoid copying the "owner" property into class B (which will consequently "denormalize" and copy the owner key into both tables generated for entity A and B). Also, I would really like to have A and B in a separate table and not have to use a discriminator by using SingleTable inheritance.
Also, I can't figure out how to do something similar by using #OneToOne between A and B (and not having B extend A).
It's a Hibernate oddity, but it's deliberate. I have a blog post up with background information, links and a workaround for the JOINED solution.
Try adding targetEntity = Transaction.class. This worked for me when I was using SINGLE_TABLE inheritance. I didn't try it with JOIN.
#Entity
class Owner {
#OneToMany(mappedBy="owner", cascade = {CascadeType.ALL}, targetEntity = Transaction.class)
#Where(clause = "tableType='I'")
protected Set<B> getBSet() {
..
}
}
I'd double check your real implementation. I used your sample code and after adding an #Id everything worked as expected. Even IntelliJ says getBSet() is associated with B.owner.