Tutorial :Why hibernate perform two queries for eager load a @OneToOne bidirectional association?



Question:

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?


Solution:1

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


Solution:2

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.


Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »