¿Cómo puedo hacer que una relación JPA OneToOne sea perezosa?

Resuelto KCL asked hace 15 años • 12 respuestas

En esta aplicación que estamos desarrollando, notamos que la vista era particularmente lenta. Hice un perfil de la vista y noté que había una consulta ejecutada por hibernación que tomó 10 segundos incluso si solo había dos objetos en la base de datos para recuperar. Todos OneToManyy ManyToManylas relaciones eran flojas así que ese no era el problema. Al inspeccionar el SQL real que se estaba ejecutando, noté que había más de 80 combinaciones en la consulta.

Al examinar más a fondo el problema, noté que el problema era causado por la profunda jerarquía OneToOney las ManyToOnerelaciones entre las clases de entidades. Entonces, pensé, simplemente los haré perezosos, eso debería resolver el problema. Pero anotar @OneToOne(fetch=FetchType.LAZY)o @ManyToOne(fetch=FetchType.LAZY)no parece funcionar. O obtengo una excepción o en realidad no se reemplazan con un objeto proxy y, por lo tanto, soy vago.

¿Alguna idea de cómo haré que esto funcione? Tenga en cuenta que no uso persistence.xmlpara definir relaciones o detalles de configuración, todo se hace en código java.

KCL avatar Sep 18 '09 19:09 KCL
Aceptado

En primer lugar, algunas aclaraciones a la respuesta de KLE :

  1. La asociación uno a uno sin restricciones (que acepta valores NULL) es la única que no se puede representar sin instrumentación de código de bytes. La razón de esto es que la entidad propietaria DEBE saber si la propiedad de la asociación debe contener un objeto proxy o NULL y no puede determinarlo mirando las columnas de su tabla base debido a que normalmente se asigna uno a uno a través de PK compartida, por lo que tiene que ser buscado con entusiasmo de todos modos, haciendo que el proxy sea inútil. Aquí hay una explicación más detallada .

  2. Las asociaciones de muchos a uno (y de uno a muchos, obviamente) no sufren este problema. La entidad propietaria puede verificar fácilmente su propia FK (y en el caso de uno a muchos, inicialmente se crea un proxy de colección vacío y se completa según demanda), por lo que la asociación puede ser vaga.

  3. Reemplazar uno a uno con uno a muchos nunca es una buena idea. Puede reemplazarlo con muchos a uno únicos, pero existen otras opciones (posiblemente mejores).

Rob H. tiene un punto válido, sin embargo, es posible que no pueda implementarlo dependiendo de su modelo (por ejemplo, si su asociación uno a uno es anulable).

Ahora, en lo que respecta a la pregunta original:

A) @ManyToOne(fetch=FetchType.LAZY)debería funcionar bien. ¿Está seguro de que no se sobrescribe en la consulta misma? Es posible especificar join fetchen HQL y/o establecer explícitamente el modo de recuperación a través de Criteria API, que tendría prioridad sobre la anotación de clase. Si ese no es el caso y aún tiene problemas, publique sus clases, consultas y SQL resultante para una conversación más directa.

B) @OneToOnees más complicado. Si definitivamente no se puede anular, siga la sugerencia de Rob H. y especifíquela como tal:

@OneToOne(optional = false, fetch = FetchType.LAZY)

De lo contrario, si puede cambiar su base de datos (agregar una columna de clave externa a la tabla de propietarios), hágalo y asígnela como "unida":

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

y en OtraEntidad:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

Si no puede hacer eso (y no puede vivir con la búsqueda ansiosa), la instrumentación de código de bytes es su única opción. Sin embargo, tengo que estar de acuerdo con CPerkins : ¡si tienes 80! se une debido a las ansiosas asociaciones OneToOne, tienes problemas mayores que este :-)

ChssPly76 avatar Sep 18 '2009 16:09 ChssPly76

Para que la carga diferida funcione en asignaciones uno a uno que aceptan valores NULL, debe permitir que Hibernate realice la instrumentación en tiempo de compilación y agregue @LazyToOne(value = LazyToOneOption.NO_PROXY)a la relación uno a uno.

Mapeo de ejemplo:

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

Ejemplo de extensión de archivo Ant Build (para realizar la instrumentación en tiempo de compilación de Hibernate):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>
Kdeveloper avatar Aug 28 '2010 13:08 Kdeveloper

A menos que esté utilizando Bytecode Enhancement, no puede recuperar de forma perezosa la @OneToOneasociación del lado principal.

Sin embargo, la mayoría de las veces, ni siquiera necesitas la asociación del lado de los padres si la usas @MapsIden el lado del niño:

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {
 
    @Id
    private Long id;
 
    @Column(name = "created_on")
    private Date createdOn;
 
    @Column(name = "created_by")
    private String createdBy;
 
    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;
 
    public PostDetails() {}
 
    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }
 
    //Getters and setters omitted for brevity
}

Con @MapsId, la idpropiedad en la tabla secundaria sirve como clave principal y clave externa para la clave principal de la tabla principal.

Entonces, si tiene una referencia a la Postentidad principal, puede recuperar fácilmente la entidad secundaria utilizando el identificador de la entidad principal:

PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);

De esta manera, no tendrá problemas de consulta N+1 que podrían ser causados ​​por la mappedBy @OneToOneasociación en el lado principal.

Vlad Mihalcea avatar Sep 15 '2019 05:09 Vlad Mihalcea

La idea básica detrás de los XToOnes en Hibernate es que en la mayoría de los casos no son perezosos.

Una razón es que, cuando Hibernate tiene que decidir poner un proxy (con la identificación) o un valor nulo,
tiene que buscar en la otra tabla de todos modos para unirse. El costo de acceder a la otra tabla en la base de datos es significativo, por lo que también podría recuperar los datos de esa tabla en ese momento (comportamiento no diferido), en lugar de recuperarlos en una solicitud posterior que requeriría un segundo acceso a la base de datos. misma mesa.

Editado: para obtener más detalles, consulte la respuesta de ChssPly76 . Este es menos preciso y detallado, no tiene nada que ofrecer. Gracias ChssPly76.

KLE avatar Sep 18 '2009 14:09 KLE

En las asignaciones XML nativas de Hibernate, puede lograr esto declarando una asignación uno a uno con el atributo restringido establecido en verdadero. No estoy seguro de cuál es el equivalente de la anotación Hibernate/JPA, y una búsqueda rápida en el documento no proporcionó respuesta, pero espero que eso le dé una pista para continuar.

Rob H avatar Sep 18 '2009 13:09 Rob H