JPA OneToOne bidirectional . - java

I have two entity classes that are in #OneToOne relation. The example code are as follow:
public class A {
#Id
private int id;
private String name;
#JoinColumn(name = "B_ID", referencedColumnName = "id")
#OneToOne(cascade=CascadeType.ALL)
private B b;
//setters and getters
}
public class B {
#Id
private int id;
private String name;
#OneToOne(mappedBy="b")
private A a;
//setter and getters
}
my question here is "Can I use setA(A a) method in class B. I mean like this . .
em.getTransaction().begin();
A aa = new A();
aa.setId(1);
aa.setName("JJ");
em.persist(aa);
B bb = new B();
bb.setId(1);
bb.setName("CC");
bb.setA(aa);
em.persist(bb);
em.getTransaction().commit();
When I tried like this, the foreign_key field in table A (B_ID) was saved as null.
Please help me.

Here , you have specified mappedBy in class B above private A a;. In a bidirectional relationship , mappedBy means that I am not the owner. So It means that A is the owner of the relationship.
In table of A , you will have a foreignkey for table of B. As A is the owner, A is suppose to cascade operations to B. Ideally you should try a.setB() and then persist a.
Try below:
em.getTransaction().begin();
//first create B.
B bb = new B();
bb.setId(1);
bb.setName("CC");
em.persist(bb);
//create A with B set in it.
A aa = new A();
aa.setId(1);
aa.setName("JJ");
aa.setB(bb);
em.persist(aa);
em.getTransaction().commit();
Or
em.getTransaction().begin();
//first create B.
B bb = new B();
bb.setId(1);
bb.setName("CC");
// no need to persist bb.
//create A with B set in it.
A aa = new A();
aa.setId(1);
aa.setName("JJ");
aa.setB(bb);
em.persist(aa); // because of cascade all , when you persist A ,
// B will also be persisted.
em.getTransaction().commit();

Use #Cascade({CascadeType.SAVE_UPDATE}) to cascade changes
public class B {
#Id
private int id;
private String name;
#OneToOne(mappedBy="b")
#Cascade({CascadeType.SAVE_UPDATE})
private A a;
//setter and getters
}

you need to add aa.setB(bb) before em.persist(bb)
em.getTransaction().begin();
A aa = new A();
aa.setId(1);
aa.setName("JJ");
em.persist(aa);
B bb = new B();
bb.setId(1);
bb.setName("CC");
aa.setB(bb);//this line should be added
bb.setA(aa);
em.persist(bb);
em.getTransaction().commit();

I had also the same problem. A class had already Setter Method for class B. My Problem was solved with setter in class B. In Class B i put method setter like this.
public void setA(A a){
this.a=a;
a.setB(this);
}
.

Related

How to cascade insert child entities when child has composite key

I am starting with JPA coming from EF and tried to do a simple master-detail insert where the child has a composite key.
The foo is inserted alright (no errors, Hibernate just printing insert into Foo statement), but the bar just gets ignored.
I found this question where the relation is defined in the key, but I couldn't get it to work either (same issue as with my original solution, no exceptions, just no child insert either).
My code currently looks like this:
#Entity
public class Foo {
#Id
private String fooID;
#OneToMany(mappedBy = "foo")
private List<Bar> bars = new ArrayList<>();
// getter, setter,...
}
#Entity
public class Bar {
#EmbeddedId
private BarId id;
public Bar(){
this.id = new BarId();
}
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#MapsId("fooID")
#JoinColumn(name = "fooID", referencedColumnName = "fooID")
private Foo foo;
public void setFooId(String fooId){
this.id.setFooId(fooId);
}
public void setBarNo(int barNo){
this.id.setBarNo(barNo);
}
// other getter, setter,...
}
#Embeddable
public class BarId implements Serializable {
private String fooID;
private int barNo;
// getter, setter, hashCode, equals,...
}
// ...
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Foo newFoo = new Foo();
newFoo.setFooID("baz");
Bar newBar = new Bar();
newBar.setFooId(newFoo.getFooId()); // even necessary?
newBar.setBarNo(1);
newBar.setFoo(newFoo);
newFoo.getBars().add(newBar);
em.persist(newFoo);
em.getTransaction().commit();
I am using JPA 2.2 with Hibernate 5.4 if that makes any difference.
To better clarify what I'm after (for everyone who knows a little EF):
foo.HasKey(f => f.FooID);
foo.HasMany(f => f.Bars).WithOne(b => b.Foo).HasForeignKey(b => b.FooID);
bar.HasKey(b => new {b.FooID, b.BarNo});
What do I have to change to get this to work?
Or am I using JPA entirely wrong to begin with?
Figured it out: the cascade = CascadeType.PERSIST has to be put on the master side and not on the detail side of the relation like so:
#OneToMany(mappedBy = "foo", cascade = CascadeType.PERSIST)
private List<Bar> bars = new ArrayList<>();

setting an id autogenerate into an object

Sorry if my post is duplicated or the tittle doesn't describe the topics, because I don't know how to describe this in the tittle, I look on internet, but I didn't find the solution.
I am using Java and JPA. The problem is the next :
I have a class A with an autogenerated key :
class A{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private List<B> listB;
}
And the class B with the id of this clas:
class B {
#EmbeddedId
private Bid id;
private String att;
}
class Bid {
private int idA;
private String text;
}
In a controller I want to create an object A, the problem is when I created the object A, I need to create the object B where the id of B contains the id of A which is autogenerated, and it is created in the moment when the entity is mapped to de database, I dont't know how to set the id autogenerated of A into the idB, maybe I should query to de database asking what is the las id of classA, but it seem bad.
Thanks in advance
Your case is a derived identifier case, where your entity B's identity was derived from the primary key of A. You can use #MapsId annotation for this case and your entities can be restructured like this:
#Entity
public class A {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#OneToMany(mappedBy="a")
private List<B> listB = new ArrayList<B>();
...
}
#Entity
public class B {
#EmbeddedId
private BId id;
#ManyToOne
#MapsId("idA")
private A a;
...
}
#Embeddable
public class BId {
private int idA;
private String att;
...
}
This is how you would persist the entities:
A a = new A();
BId bid = new BId();
bid.setAtt("text"); // notice that the idA attribute is never manually set, since it is derived from A
B b = new B();
b.setId(bid);
b.setA(a);
a.getListB().add(b);
em.persist(a);
em.persist(b);
See sample implementation here.
It would be useful to know which is the case scenario you are trying to solve in general because the structure you are using seems unnecessarily complex.
What is your real goal?

Spring data jpa save

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.

Hibernate: Merging existing Entities with new "Double Parented" Children

I'm having some difficulties to put a specific scenario to work with Hibernate...
Considering the snippet above (simplified model), Entities A and D already exist. They will receive new Children B and E, and E has also new C Childrens. Then I call the "merge" method in Entity Manager.
#Entity
public class A {
#OneToMany(mappedBy = "a", fetch = FetchType.Lazy, cascade = CascadeType.All)
private List<B> bList;
}
#Entity
public class B {
#ManyToOne(fetch=FetchType.LAZY)
#JoinCollumn(name="ID_A")
private A a;
#OneToMany(mappedBy = "b", fetch = FetchType.Lazy, cascade = CascadeType.All)
private List<E> eList;
}
#Entity
public class C {
#ManyToOne(fetch=FetchType.LAZY)
#JoinCollumn(name="ID_E")
private E e;
}
#Entity
public class D {
#OneToMany(mappedBy = "d", fetch = FetchType.Lazy, cascade = CascadeType.All)
private List<E> bList;
}
// The "Double Parented" Children
#Entity
public class E {
#ManyToOne(fetch=FetchType.LAZY)
#JoinCollumn(name="ID_B")
private B b;
#ManyToOne(fetch=FetchType.LAZY)
#JoinCollumn(name="ID_D")
private D d;
#OneToMany(mappedBy = "e", fetch = FetchType.Lazy, cascade = CascadeType.All)
private List<C> cList;
}
public class Example {
public void newChildren() {
A a = getExistingAfromDatabase(); // defined elsewhere
E e = new E(); // then fills some attributes...
B b = new B(); // then fills some attributes...
b.getEs().add(e);
e.setB(b);
a.getBs().add(b);
b.setA(a);
edit(a);
}
public void edit(A myEntity) {
EntityTransaction trans = null;
EntityManager eMngr = getEntityManager(); // this is defined in another place
try {
trans = eMngr.getTransaction();
trans.begin();
eMngr.merge(myEntity);
trans.commit();
} catch (Exception e) {
if (trans != null && trans.isActive())
trans.rollback();
throw new DAOException("Error! " + e.getMessage(), e);
}
}
}
Whichever Entity I choose to merge first (A or D), I got the same exception: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: E.b -> B
Is Hibernate able to handle this with Cascade? If so, what have I done wrong?
Thanks a lot!
Make sure you always set both the Parent and the Child, so taking A and B you should have this kind of utility method in your base class (e.g. A):
public void addChild(B child) {
child.setA(this);
this.bList.add(child);
}
This utilities should prevent you from getting this type of exceptions.

Why hibernate perform two queries for eager load a #OneToOne bidirectional association?

i have entity A that has-a B entity, and B has-a A with #OneToOne bidirectional association.
Now, when i findall A records, hibernate perform two queries with a left outer join on B, something like this:
select a.id, a.id_b, a.field1, b.id, b.field1 from A as a, B as b left outer join b ON b.id=a.id_b;
select a.id, a.id_b, a.field1, b.id, b.field1 from A as a, B as b left outer join b ON b.id=a.id_b WHERE b.id=?
First query load A and B fields and it is ok, but why perform second query to reload A?
I think this query load the A content in B, but this A is obviusly the A that contains B... so its already loaded with first query, isn't true?
-- EDIT --
Entity A:
#Entity
public class A implements Serializable{
// id and other ecc ecc
#OneToOne
#JoinColumn(name="id_b")
B b;
}
Entity B:
#Entity
public class B implements Serializable{
// id and other ecc ecc
#OneToOne(mappedBy="b")
A a;
}
This is the situation, and a findAll on A need two queries... why?
Blow, if A and B share The same primary key column where both entities are joined by using their primary key, you should use #PrimaryKeyJoinColumn instead
#Entity
public class A implements Serializable {
private MutableInt id = new MutableInt();
private B b;
public void setIdAsMutableInt(MutableInt id) {
this.id = id;
}
#Id
#GeneratedValue
public Integer getId() {
return id.intValue();
}
public void setId(Integer id) {
this.id.setValue(id);
}
/**
* Any ToOne annotation, such as #OneToOne and #ManyToOne, is EARGELY loaded, by default
*/
#OneToOne(fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
#Cascade(CascadeType.SAVE_UPDATE)
public B getB() {
return b;
}
public void setB(B b) {
b.setIdAsMutableInt(id);
this.b = b;
}
}
And B Notice you do not need mappedBy attribute because of #PrimaryKeyJoinColumn
#Entity
public class B implements Serializable {
private MutableInt id = new MutableInt();
private A a;
public void setIdAsMutableInt(MutableInt id) {
this.id = id;
}
#Id
public Integer getId() {
return id.intValue();
}
public void setId(Integer id) {
this.id.setValue(id);
}
#OneToOne(fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
Let's Test (You can Test if you want)
A a = new A();
B b = new B();
a.setB(b);
/**
* b property will be saved because Cascade.SAVE_UPDATE
*/
Serializable id = session.save(a);
b = (B) session
.createQuery("from B b left join fetch b.a where b.id = :id")
.setParameter("id", id)
.list()
.get(0);
Assert.assertEquals(b.getId(), b.getA().getId());
Notice I use a MutableInt field (encapsulated by a Integer property) instead of Integer because Integer is a immutable Type as a way Both A and B share The SAME assigned id
But if A and B are joined by using other Than their primary key, you should use #JoinColumn and mappedBy (bi-directional relationship, right) as follows
#Entity
public class A implements Serializable {
private Integer id;
private B b;
#Id
#GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
/**
* mappedBy="a" means: Look at "a" field / property at B Entity. If it has any assigned value, join us Through B_ID foreign key column
*/
#OneToOne(fetch=FetchType.LAZY, mappedBy="a")
/**
* Table A has a foreign key column called "B_ID"
*/
#JoinColumn(name="B_ID")
#Cascade(CascadeType.SAVE_UPDATE)
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
And B
#Entity
public class B implements Serializable {
private Integer id;
private A a;
public void setIdAsMutableInt(MutableInt id) {
this.id = id;
}
#Id
#GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#OneToOne(fetch=FetchType.LAZY)
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
To test
A a = new A();
B b = new B();
/**
* Set up both sides
* Or use some kind of add convenience method
*/
a.setB(b);
b.setA(a);
/**
* b property will be saved because Cascade.SAVE_UPDATE
*/
Serializable id = session.save(a);
b = (B) session
.createQuery("from B b left join fetch b.a where b.id = :id")
.setParameter("id", id)
.list()
.get(0);
By using The owner side B, you will get Two select statements It occurs because B Table does not contain any foreign key column which points To Table A But by using
"from A a left join fetch a.b where a.id = :id"
You will get just one select statement because A knows how To retrieve its joined B by using its B_ID foreign key column
What does your mapping look like exactly?
Do your A and B classes correctly implement hashCode() and equals() so that Hibernate can tell that the A instance pointed to by B is the same instance of the first A?
Sounds like you are trying to model a bi-directional one-to-one mapping - take a look at the section in the manual on this to see the recommended methods for accomplishing it.

Categories

Resources