-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 
Author Message
 Post subject: One-to-one - bidirectional - not lazy - N+1 select problem
PostPosted: Mon Sep 05, 2011 12:15 pm 
Newbie

Joined: Mon Sep 05, 2011 11:27 am
Posts: 4
Hello,

I am not able to define a mapping that retreive A or B (linked with a single not lazy bidirectional One-to-One association) with a single select.
I worked with @LazyToOne, EAGER, optional, fetch="join", "hibernate.max_fetch_depth", FetchProfile ... but I did not find how to achieve a single select.
I know that we can define this association with an other mapping like @PrimaryKeyJoinColumn and that a One-to-One mapping can be a bad design.

Why do I have several queries when I retreive an element with a one-to-one association defined with the following mapping ?
I am not looking for another mapping, I only want to understand why I have several queries.
If it is possible, how can I modify the mapping to solve the N+1 select problem.

In the following test case, I expected to send only one query to retreive A or B but I have respectively 3 and 2 select queries.

Best regards,

Vincent

Here is a simple test case :
Hibernate 3.6.5

hibernate.cfg.xml
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
   <!-- Config -->
   <!-- http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#session-configuration -->
   <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
   <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
   <property name="hibernate.connection.pool_size">10</property>
   
   <property name="hibernate.show_sql">true</property>
   <property name="hibernate.format_sql">false</property>
   
   <property name="hibernate.max_fetch_depth">1</property>
   <property name="hibernate.connection.autocommit">false</property>
   <property name="hibernate.cache.use_second_level_cache">false</property>
   <property name="hibernate.query.substitutions">yes 'Y', no 'N'</property>
   
   <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/testhibernate</property>
   <property name="hibernate.connection.username">username</property>
   <property name="hibernate.connection.password">password</property>
   
   <!-- mapping files -->
   <mapping class="fr.mycompany.hibernate.test.bo.A"/>
    <mapping class="fr.mycompany.hibernate.test.bo.B"/>
  </session-factory>
</hibernate-configuration>


Super class of A and B
Code:
package fr.mycompany.hibernate.test.bo;

import java.io.Serializable;
import java.util.Calendar;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

@MappedSuperclass
public abstract class BusinessObject implements Serializable {

   private static final long serialVersionUID = 4179574695380922867L;

   /**
    * Technical primary key
    */
   protected Long id = null;

   protected Calendar timeStamp = null;

   public BusinessObject() {
   }

   @Id
   @Column(name = "ID", nullable = false)
   @GeneratedValue(strategy = GenerationType.AUTO)
   public Long getId() {
      return id;
   }

   public void setId(Long id) {
      this.id = id;
   }

   public void setId(long id) {
      this.id = new Long(id);
   }

   @Version
   @Column(name = "OBJ_OPT_LOCK", nullable = false)
   public Calendar getTimeStamp() {
      return timeStamp;
   }

   public void setTimeStamp(Calendar ts) {
      timeStamp = ts;
   }

   public abstract String asString();
}


A.java
Code:
package fr.mycompany.hibernate.test.bo;

import javax.persistence.Entity;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

@Entity
@Table(name = "A")
public class A extends BusinessObject {

   private static final long serialVersionUID = 87345767169183337L;

   public final static String BEAN_B = "b";

   private B b;

   public A() {
      super();
   }

   @OneToOne()
   @Cascade(value = { CascadeType.ALL })
   public B getB() {
      return b;
   }

   public void setB(B b) {
      this.b = b;
   }

   public void setRelationB(B value) {
      B newValue = value;
      B oldValue = getB();
      if (oldValue != null)
         oldValue.setA(null);
      if (newValue != null) {
         newValue.setRelationA(null);
         newValue.setA(this);
      }
      setB(value);
   }

   public void addB(B b) {
      setRelationB(b);
   }

   public void removeB(B b) {
      if (getB().equals(b))
         setB(null);
   }

   /**
    * Safely gets B from a B.
    * @return B
    */
   @Transient
   public B getRelationB() {
      return getB();
   }

   @Override
   public String asString() {
      return super.toString() + " -> " + getB().getId();
   }

}


B.java
Code:
package fr.mycompany.hibernate.test.bo;

import javax.persistence.Entity;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;

@Entity
@Table(name = "B")
public class B extends BusinessObject {

   private static final long serialVersionUID = -5733931756299476473L;

   public final static String BEAN_A = "a";

   private A a;

   public B() {
      super();
   }

   @OneToOne(mappedBy = "b")
   @Cascade(value = { CascadeType.ALL })
   public A getA() {
      return a;
   }

   public void setA(A a) {
      this.a = a;
   }

   public void setRelationA(A value) {
      A newValue = value;
      A oldValue = getA();
      if (oldValue != null)
         oldValue.setB(null);
      if (newValue != null) {
         newValue.setRelationB(null);
         newValue.setB(this);
      }
      setA(value);
   }

   public void addA(A a) {
      setRelationA(a);
   }

   public void removeA(A a) {
      if (getA().equals(a))
         setA(null);
   }

   /**
    * Safely gets A from a A.
    * @return A
    */
   @Transient
   public A getRelationA() {
      return getA();
   }

   @Override
   public String asString() {
      return super.toString() + " -> " + getA().getId();
   }
}


ExportSchema.java
Code:
package fr.mycompany.hibernate;

import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;

public class ExportSchema {

   public static void main(String[] args) {
      Configuration configuration = new Configuration().configure();
      SchemaExport schemaExport = new SchemaExport(configuration);
      schemaExport.create(true, true);
   }
}


HibernateUtil.java
Code:
package fr.mycompany.hibernate;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

   private static final SessionFactory sessionFactory;
   static {
      try {
         sessionFactory = new Configuration().configure().buildSessionFactory();
      } catch (Throwable ex) {
         System.err.println("Initial SessionFactory creation failed." + ex);
         throw new ExceptionInInitializerError(ex);
      }
   }

   public static SessionFactory getSessionFactory() {
      return sessionFactory;
   }
}


Classe de test => MainAB.java
Code:
package fr.mycompany.hibernate.test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;

import fr.mycompany.hibernate.ExportSchema;
import fr.mycompany.hibernate.HibernateUtil;
import fr.mycompany.hibernate.test.bo.A;
import fr.mycompany.hibernate.test.bo.B;
import fr.mycompany.hibernate.test.bo.BusinessObject;

public class MainAB {

   public static void main(String[] args) {
      ExportSchema.main(null);

      A a = new A();
      B b = new B();
      a.setRelationB(b);

      System.err.println("<!-- Save Begin -->");
      save(a);
      System.err.println("<!-- Save End -->");

      List<? extends BusinessObject> bos = null;

      System.out.println("\n###################### A retrieve/list");
      bos = listBusinessObject(A.class);
      System.err.println("---------------------- A asString");
      System.err.println(bos.iterator().next().asString());

      System.out.println("\n###################### B retrieve/list");
      bos = listBusinessObject(B.class);
      System.err.println("---------------------- B asString");
      System.err.println(bos.iterator().next().asString());
   }

   public static void save(BusinessObject boToSave) {
      Session session = HibernateUtil.getSessionFactory().openSession();
      Transaction transaction = null;
      try {
         transaction = session.beginTransaction();
         if (boToSave != null) {
            session.saveOrUpdate(boToSave);
         }
         session.flush();
         transaction.commit();
      } catch (HibernateException e) {
         transaction.rollback();
         e.printStackTrace();
         throw e;
      } finally {
         session.close();
      }
   }

   @SuppressWarnings({ "finally", "unchecked" })
   public static <T> List<T> listBusinessObject(Class<T> boClass) {
      List<T> objects = null;

      Session session = HibernateUtil.getSessionFactory().openSession();
      Transaction transaction = null;
      try {
         transaction = session.beginTransaction();
         objects = session.createQuery("from " + boClass.getSimpleName()).list();
         transaction.commit();
      } catch (HibernateException e) {
         transaction.rollback();
         e.printStackTrace();
         throw e;
      } finally {
         //         Session not closed to allow lazy loading in the Main method         
         //         session.close();
         return objects;
      }
   }
}


Here is the output :
Quote:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
alter table A drop foreign key FK418D4C45E8
alter table ADDRESS drop foreign key FKE66327D4D12962EC
alter table HOUSE drop foreign key FK41BCF0017066CE
drop table if exists A
drop table if exists ADDRESS
drop table if exists B
drop table if exists HOUSE
drop table if exists PERSON
create table A (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, b_ID bigint, primary key (ID)) ENGINE=InnoDB
create table ADDRESS (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, ADDRESSNAME varchar(255) not null, person_ID bigint, primary key (ID)) ENGINE=InnoDB
create table B (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, primary key (ID)) ENGINE=InnoDB
create table HOUSE (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, HOUSENAME varchar(255) not null, owner_ID bigint, primary key (ID)) ENGINE=InnoDB
create table PERSON (ID bigint not null auto_increment, OBJ_OPT_LOCK datetime not null, PERSONNAME varchar(255) not null, primary key (ID), unique (PERSONNAME)) ENGINE=InnoDB
alter table A add index FK418D4C45E8 (b_ID), add constraint FK418D4C45E8 foreign key (b_ID) references B (ID)
alter table ADDRESS add index FKE66327D4D12962EC (person_ID), add constraint FKE66327D4D12962EC foreign key (person_ID) references PERSON (ID)
alter table HOUSE add index FK41BCF0017066CE (owner_ID), add constraint FK41BCF0017066CE foreign key (owner_ID) references PERSON (ID)
<!-- Save Begin -->
Hibernate: insert into B (OBJ_OPT_LOCK) values (?)
Hibernate: insert into A (b_ID, OBJ_OPT_LOCK) values (?, ?)
<!-- Save End -->

###################### A retrieve/list
Hibernate: select a0_.ID as ID8_, a0_.b_ID as b3_8_, a0_.OBJ_OPT_LOCK as OBJ2_8_ from A a0_
Hibernate: select b0_.ID as ID9_1_, b0_.OBJ_OPT_LOCK as OBJ2_9_1_, a1_.ID as ID8_0_, a1_.b_ID as b3_8_0_, a1_.OBJ_OPT_LOCK as OBJ2_8_0_ from B b0_ left outer join A a1_ on b0_.ID=a1_.b_ID where b0_.ID=?
Hibernate: select a0_.ID as ID8_1_, a0_.b_ID as b3_8_1_, a0_.OBJ_OPT_LOCK as OBJ2_8_1_, b1_.ID as ID9_0_, b1_.OBJ_OPT_LOCK as OBJ2_9_0_ from A a0_ left outer join B b1_ on a0_.b_ID=b1_.ID where a0_.b_ID=?
---------------------- A asString
fr.lactalis.hibernate.test.bo.A@109b2a51 -> 1

###################### B retrieve/list
Hibernate: select b0_.ID as ID9_, b0_.OBJ_OPT_LOCK as OBJ2_9_ from B b0_
Hibernate: select a0_.ID as ID8_1_, a0_.b_ID as b3_8_1_, a0_.OBJ_OPT_LOCK as OBJ2_8_1_, b1_.ID as ID9_0_, b1_.OBJ_OPT_LOCK as OBJ2_9_0_ from A a0_ left outer join B b1_ on a0_.b_ID=b1_.ID where a0_.b_ID=?
---------------------- B asString
fr.lactalis.hibernate.test.bo.B@782cbc86 -> 1


Top
 Profile  
 
 Post subject: Re: One-to-one - bidirectional - not lazy - N+1 select problem
PostPosted: Tue Sep 06, 2011 9:41 am 
Newbie

Joined: Mon Sep 05, 2011 11:27 am
Posts: 4
I finally understand a part of my problem !
Here is the link http://stackoverflow.com/questions/2931936/hibernate-noob-fetch-join-problem

To sum up, it comes from how I am quering the DB:
I should use
Code:
objects = session.createCriteria(boClass).list();
instead of
Code:
objects = session.createQuery("from " + boClass.getSimpleName()).list();
I think when you are text writting the query or part of the query then you need to write join in the query.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.