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.xmlCode:
<?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 BCode:
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.javaCode:
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.javaCode:
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.javaCode:
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.javaCode:
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.javaCode:
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