onetoone one mapsid manytomany foreign example ejemplo anotacion java hibernate jpa

java - mapsid - Haciendo perezoso a OneToOne-relation



onetoone jpa ejemplo (6)

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

Al inspeccionar más el problema, noté que el problema fue causado por la profunda jerarquía de las relaciones OneToOne y ManyToOne entre las clases de entidad. Entonces, pensé, solo los haré vagos, eso debería resolver el problema. Pero anotar @OneToOne(fetch=FetchType.LAZY) o @ManyToOne(fetch=FetchType.LAZY) no parece funcionar. O recibo una excepción o, en realidad, no se reemplazan con un objeto proxy y, por lo tanto, soy flojo.

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


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

En lugar de utilizar @OneToOne en ambos lados, utilizo @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 de forma perezosa, es decir, las selecciones solo se realizan cuando se getPrevious() o getNext() , y solo se selecciona una 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); } }


Como ya explica perfectamente ChssPly76, los proxies de Hibernate no ayudan con las asociaciones uno-a-uno sin restricciones (anulables), PERO aquí se explica un truco para evitar configurar la instrumentación. La idea es engañar a Hibernate para que la clase de entidad que queremos utilizar ya esté instrumentada: la instrumentamos manualmente en el código fuente. ¡Es fácil! Lo he implementado con CGLib como proveedor de código de bytes 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 la instrumentación real (me refiero a automática) cuando tienes solo una relación nulable de uno a uno que deseas hacer perezoso. El inconveniente principal es que la solución depende del proveedor de códigos de bytes que está utilizando, así que coméntela con precisión porque podría tener que cambiar el proveedor de códigos de bytes en el futuro; por supuesto, también está modificando su modelo de frijol por una razón técnica y esto no está bien.


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


La idea básica detrás de XToOnes en Hibernate es que no son flojos 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 nulo,
tiene que mirar en la otra mesa de todos modos para unirse. El costo de acceder a la otra tabla en la base de datos es significativo, por lo que 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.


Para obtener la carga diferida trabajando en asignaciones nulos uno a uno, debe dejar que hibernate compile la instrumentación de tiempo y agregue un @LazyToOne(value = LazyToOneOption.NO_PROXY) a la relación de 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 hacer la instrumentación Hibernate en tiempo de compilació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>


Primero, algunas aclaraciones a la respuesta de KLE :

  1. La asociación uno-a-uno sin restricciones (nulable) es la única que no puede ser procesada sin instrumentación de bytecode. La razón para 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 la tabla base debido a que uno a uno se correlaciona normalmente mediante una PK compartida, por lo que tiene que ser buscado con entusiasmo de todos modos haciendo el proxy inútil. Aquí hay una explicación más detallada .

  2. las asociaciones de muchos a uno (y uno-a-muchos, obviamente) no sufren de este problema. La entidad propietaria puede verificar fácilmente su propio FK (y en el caso de uno a muchos, el proxy de recopilación vacío se crea inicialmente y se llena a petición), por lo que la asociación puede ser floja.

  3. Reemplazar uno a uno con uno a muchos nunca es una buena idea. Puede reemplazarlo con muchos de uno en uno, pero hay otras opciones (posiblemente mejores).

Rob H. tiene un punto válido, sin embargo, es posible que no pueda implementarlo según 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ás seguro de que no se sobrescribe en la consulta en sí? Es posible especificar la join fetch en HQL y / o establecer explícitamente el modo de búsqueda a través de Criteria API, que tendrá prioridad sobre la anotación de clase. Si ese no es el caso y aún tiene problemas, publique sus clases, consulta y SQL resultante para obtener más conversaciones al momento.

B) @OneToOne es más complicado. Si definitivamente no puede contener nulos, siga 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 (agregue una columna de clave externa a la tabla de propietario), hágalo y asóciela como "unida":

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

y en OtherEntity:

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

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