2010-08-01 13 views
8

tengo la entidad A que tiene una entidad B, y B tiene una asociación A con @OneToOne bidireccional.¿Por qué hibernate realiza dos consultas para cargar ansiosamente una asociación bidireccional @OneToOne?

Ahora, cuando me FindAll registros A, hibernación realizar dos consultas con una combinación externa izquierda de B, algo como esto:

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=? 

campos de carga Primera consulta A y B y que está bien, pero ¿por qué realizar segunda consulta para volver a cargar A? Creo que esta consulta carga el contenido A en B, pero esta A es evidentemente la A que contiene B ... por lo que ya está cargada con la primera consulta, ¿no es verdad?

- EDITAR -

entidad A:

@Entity 
public class A implements Serializable{ 
    // id and other ecc ecc 
    @OneToOne 
    @JoinColumn(name="id_b") 
    B b; 
} 

entidad B:

@Entity 
public class B implements Serializable{ 
    // id and other ecc ecc 
    @OneToOne(mappedBy="b") 
    A a; 
} 

Ésta es la situación, y un findAll en una necesidad de dos consultas .. . ¿por qué?

Respuesta

6

Blow, si A y B comparten la misma columna de clave principal donde ambas entidades se unen por medio de su clave primaria, se debe utilizar en lugar @PrimaryKeyJoinColumn

@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; 
    } 

} 

Y B Aviso haces no es necesario mappedBy atributo debido @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; 
    } 

} 
Prueba

Vamos (Se puede probar si quieres)

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()); 

Aviso que utiliza un campo MutableInt (encapsulado por una propiedad de entero) en lugar de entero debido Entero es un tipo inmutable como una forma Tanto A como B comparten el mismo ID asignado

Pero si A y B están unidas por el uso de su clave primaria, se debe utilizar @JoinColumn y mappedBy (relación bidireccional, a la derecha) de la siguiente

@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; 
    } 

} 

Y 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; 
    } 

} 

Para probar

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); 

Usando El lado B propietario, obtendrá dos sentencias de selección Se produce porque B Tabla no contiene ninguna clave externa columna que apunta a la Tabla A Pero al usar

"de la A a la izquierda join fetch ab donde a.id =: id"

Usted recibirá sólo una instrucción de selección debido a que A sabe cómo recuperar su B unido mediante el uso de su B_ID extranjera columna de clave

+0

¡Gracias! Muy exhaustivo! – blow

0

¿Cómo se ve exactamente tu mapeo?

¿Sus A y B clases implementan correctamente hashCode() y equals() de manera que Hibernate puede decir que la instancia A apuntado por B es la misma instancia de la primera A?

Parece que está intentando modelar un mapeo uno a uno bidireccional: eche un vistazo a the section in the manual on this para ver los métodos recomendados para lograrlo.

+0

He editado mi publicación. Sí, tengo una implementación muy simple de equals y hashCode basados ​​simplemente en ID. Todos mis servicios extienden una AbstractEntity con esta implementación. – blow

Cuestiones relacionadas