Hacer que una relación de uno sea perezosa


En esta aplicación que estamos desarrollando, notamos que una vista era particularmente lenta. Perfilé la vista y noté que había una consulta ejecutada por hibernate que tomó 10 segundos incluso si solo había dos objetos en la base de datos para obtener. Todas las relaciones OneToMany y ManyToMany eran perezosas, así que ese no era el problema. Al inspeccionar el SQL real que se está ejecutando, noté que había más de 80 joins en la consulta.

Inspeccionando más a fondo el problema, me di cuenta de que el problema era causado por la profunda jerarquía de OneToOne y ManyToOne relaciones entre clases de entidades. Por lo tanto, pensé, voy a hacer que sean perezosos, que 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 entonces no son realmente reemplazados con un objeto proxy y por lo tanto ser perezoso.

¿Alguna idea de cómo voy a hacer que esto funcione? Tenga en cuenta que no uso el persistence.xml para definir relaciones o detalles de configuración, todo se hace en código java.

Author: skaffman, 2009-09-18

6 answers

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

  1. La asociación uno a uno sin restricciones (anulable) es la única que no puede ser proxy sin instrumentación de código de bytes. La razón de esto es que la entidad propietaria DEBE saber si la propiedad de asociación debe contener un objeto proxy o NULL y no puede determinar eso mirando las columnas de su tabla base debido a que uno a uno normalmente se asigna a través de PK compartido, por lo que tiene que ser buscada ansiosamente de todos modos proxy sin sentido. Aquí hay una explicación más detallada .

  2. Las asociaciones de muchos a uno (y de uno a muchos, obviamente) no sufren de este problema. La entidad propietaria puede comprobar fácilmente su propio FK (y en el caso de uno a muchos, el proxy de colección vacía se crea inicialmente y se rellena bajo demanda), por lo que la asociación puede ser perezosa.

  3. Reemplazar uno-a-uno con uno-a-muchos casi nunca es una buena idea. Puede reemplazarlo con muchos a uno únicos, pero hay 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 nullable).

Ahora, en cuanto a la pregunta original:

A) @ManyToOne(fetch=FetchType.LAZY) debería funcionar bien. ¿Estás seguro de que no está siendo sobrescrito en la consulta en sí? Es posible especificar join fetch en HQL y / o establecer explícitamente el modo de recuperación a través de la API de criterios 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 el SQL resultante para una conversación más precisa.

B) @OneToOne es más complicado. Si definitivamente no es nullable, vaya con la sugerencia de Rob H. y especifíquelo como tal:

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

De lo contrario, si puede cambiar su base de datos (agregar una columna de clave foránea a la tabla owner), hágalo y mapéela como "unida":

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

Y en otra entidad:

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

Si no puedes hacer eso (y no puede vivir con la búsqueda ansiosa) la instrumentación de código de bytes es su única opción. Tengo que estar de acuerdo con CPerkins , sin embargo-si usted tiene 80!!! se une debido a las asociaciones ansiosas de OneToOne, tienes problemas más grandes que esto: -)

 186
Author: ChssPly76,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-22 17:21:04

Para que la carga perezosa funcione en asignaciones uno a uno nullables, debe dejar que hibernate haga compile la instrumentación en tiempo y agregue un @LazyToOne(value = LazyToOneOption.NO_PROXY) a la relación uno a uno.

Ejemplo de mapeo:

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

Ejemplo de extensión de archivo de compilación Ant (para hacer la instrumentación de tiempo de compilación de Hibernación):

<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>
 17
Author: Kdeveloper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-28 14:36:10

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

Una razón es que, cuando Hibernate tiene que decidir poner un proxy (con el id) o un null,
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 perezoso), en lugar de recuperarlos en una solicitud posterior que requeriría un segundo acceso a la misma mesa.

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

 10
Author: KLE,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-20 05:53:55

Aquí hay algo que ha estado funcionando para mí (sin instrumentación):

En lugar de usar @OneToOne en ambos lados, uso @OneToMany en la parte inversa de la relación (la que tiene mappedBy). Eso hace que la propiedad sea una colección (List en el ejemplo a continuación), pero la traduzco en un elemento en el getter, haciéndolo transparente para los clientes.

Esta configuración funciona perezosamente, es decir, los selects solo se realizan cuando se llama a getPrevious() o getNext() - y solo uno selecciona para cada llamada.

La estructura de la tabla:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

La clase:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}
 7
Author: acdcjunior,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-09 22:48:09

En asignaciones XML nativas de Hibernación, puede lograr esto declarando una asignación one-to-one con el atributo constrained establecido en true. No estoy seguro de cuál es el equivalente de la anotación de hibernación/APP, y una búsqueda rápida del doc no proporcionó ninguna respuesta, pero espero que eso les dé una pista para continuar.

 5
Author: Rob H,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-18 13:42:32

Como ya ha explicado perfectamente ChssPly76, los proxies de Hibernate no ayudan con asociaciones uno a uno sin restricciones (anulables), PERO hay un truco explicado aquí para evitar configurar instrumentación. La idea es engañar a Hibernar que la clase de entidad que queremos usar ya ha sido instrumentada: la instrumentas manualmente en el código fuente. Es fácil! Lo he implementado con CGLib como proveedor de bytecode y funciona (asegúrese de configurar lazy="no-proxy" y fetch= "select", no" join", en su HBM).

Creo que esta es una buena alternativa a real (me refiero a la instrumentación automática) cuando solo tienes una relación anulable de uno a uno que quieres hacer perezosa. El principal inconveniente es que la solución depende del proveedor de bytecode que esté utilizando, por lo que comente su clase con precisión porque podría tener que cambiar el proveedor de bytecode en el futuro; por supuesto, también está modificando su modelo bean por una razón técnica y esto es no está bien.

 3
Author: Pino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-14 13:35:58