java ejemplos - En una asociación bidireccional JPA OneToMany / ManyToOne, ¿qué se entiende por "el lado inverso de la asociación"?





tutorial hibernate (5)


Para entender esto, debes dar un paso atrás. En OO, el cliente posee los pedidos (los pedidos son una lista en el objeto del cliente). No puede haber un pedido sin un cliente. Entonces el cliente parece ser el dueño de los pedidos.

Pero en el mundo SQL, un elemento contendrá un puntero al otro. Como hay 1 cliente para N pedidos, cada pedido contiene una clave externa para el cliente al que pertenece. Esta es la "conexión" y esto significa que la orden "posee" (o contiene literalmente) la conexión (información). Esto es exactamente lo opuesto al mundo OO / modelo.

Esto puede ayudar a entender:

public class Customer {
     // This field doesn't exist in the database
     // It is simulated with a SQL query
     // "OO speak": Customer owns the orders
     private List<Order> orders;
}

public class Order {
     // This field actually exists in the DB
     // In a purely OO model, we could omit it
     // "DB speak": Order contains a foreign key to customer
     private Customer customer;
}

El lado inverso es el OO "propietario" del objeto, en este caso el cliente. El cliente no tiene columnas en la tabla para almacenar las órdenes, por lo que debe indicarle en qué parte de la tabla de órdenes puede guardar estos datos (lo que sucede a través de mappedBy ).

En estos ejemplos en TopLink JPA Annotation Reference :

Ejemplo 1-59 @OneToMany - Clase de cliente con genéricos

@Entity
public class Customer implements Serializable {
    ...
    @OneToMany(cascade=ALL, mappedBy="customer")
    public Set<Order> getOrders() { 
        return orders; 
    }
    ...
}

Ejemplo 1-60 @ManyToOne - Ordenar clase con genéricos

@Entity
public class Order implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="CUST_ID", nullable=false)
    public Customer getCustomer() { 
        return customer; 
    }
    ...
}

Me parece que la entidad Customer es el propietario de la asociación. Sin embargo, en la explicación del atributo mappedBy en el mismo documento, está escrito que:

si la relación es bidireccional, establezca el elemento mappedBy en el lado inverso (no propietario) de la asociación con el nombre del campo o propiedad que posee la relación como se muestra en el Ejemplo 1-60.

Sin embargo, si no me equivoco, parece que en el ejemplo, el mappedBy está realmente especificado en el lado propietario de la asociación, en lugar del lado no propietario.

Entonces mi pregunta es básicamente:

  1. En una asociación bidireccional (de uno a muchos / muchos a uno), ¿cuál de las entidades es el propietario? ¿Cómo podemos designar a One Side como propietario? ¿Cómo podemos designar al lado de Muchos como el dueño?

  2. ¿Qué se entiende por "el lado inverso de la asociación"? ¿Cómo podemos designar el lado uno como el inverso? ¿Cómo podemos designar al lado de Muchos como el inverso?




La entidad que tiene la tabla con la clave externa en la base de datos es la entidad propietaria y la otra tabla, a la que se apunta, es la entidad inversa.




Increíblemente, en 3 años nadie ha respondido a su excelente pregunta con ejemplos de ambas maneras de mapear la relación.

Como lo mencionaron otros, el lado "propietario" contiene el puntero (clave externa) en la base de datos. Puede designar a cualquiera de los lados como propietario, sin embargo, si designa el lado Uno como propietario, la relación no será bidireccional (el lado inverso "muchos" no tendrá conocimiento de su "propietario"). Esto puede ser deseable para la encapsulación / acoplamiento flexible:

// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
    @OneToMany(cascade = CascadeType.ALL)
    private List<Order> orders;
}

// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
    // @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}

La única solución de mapeo bidireccional es que el lado "muchos" tenga su puntero al "uno", y use el atributo @OneToMany "mappedBy". Sin el atributo "mappedBy", Hibernate esperará una asignación doble (la base de datos tendría tanto la columna de unión como la tabla de unión, que es redundante (por lo general no deseable)).

// "One" Customer as the inverse side of the relationship
public class Customer {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
    private List<Order> orders;
}

// "many" orders each own their pointer to a Customer
public class Order {
    @ManyToOne
    private Customer customer;
}



Reglas simples de relaciones bidireccionales:

1. Para las relaciones bidireccionales de muchos a uno, los muchos lados siempre son el lado dueño de la relación. Ejemplo: 1 habitación tiene muchas personas (una persona pertenece solo a una habitación) -> propietario es una persona

2. Para relaciones bidireccionales uno a uno, el lado propietario corresponde al lado que contiene la clave externa correspondiente.

3. Para muchas relaciones bidireccionales, cualquiera de las partes puede ser el lado propietario.

Hope puede ayudarte.




Encontré la pregunta interesante y creé una pequeña herramienta para ti:

https://github.com/kriegaex/ThreadSafeClassLoader

Actualmente no está disponible como versión oficial en Maven Central todavía, pero puede obtener una instantánea como esta:

<dependency>
  <groupId>de.scrum-master</groupId>
  <artifactId>threadsafe-classloader</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

<!-- (...) -->

<repositories>
  <repository>
    <snapshots>
      <enabled>true</enabled>
    </snapshots>
    <id>ossrh</id>
    <name>Sonatype OSS Snapshots</name>
    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
  </repository>
</repositories>

Clase ThreadSafeClassLoader :

Utiliza JCL (Jar Class Loader) bajo el capó porque ya ofrece funciones de carga de clases, creación de instancias de objetos y generación de proxy analizadas en otras partes de este hilo. (¿Por qué reinventar la rueda?) Lo que agregué en la parte superior es una interfaz agradable para exactamente lo que necesitamos aquí:

package de.scrum_master.thread_safe;

import org.xeustechnologies.jcl.JarClassLoader;
import org.xeustechnologies.jcl.JclObjectFactory;
import org.xeustechnologies.jcl.JclUtils;
import org.xeustechnologies.jcl.proxy.CglibProxyProvider;
import org.xeustechnologies.jcl.proxy.ProxyProviderFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ThreadSafeClassLoader extends JarClassLoader {
  private static final JclObjectFactory OBJECT_FACTORY = JclObjectFactory.getInstance();

  static {
    ProxyProviderFactory.setDefaultProxyProvider(new CglibProxyProvider());
  }

  private final List<Class> classes = new ArrayList<>();

  public static ThreadLocal<ThreadSafeClassLoader> create(Class... classes) {
    return ThreadLocal.withInitial(
      () -> new ThreadSafeClassLoader(classes)
    );
  }

  private ThreadSafeClassLoader(Class... classes) {
    super();
    this.classes.addAll(Arrays.asList(classes));
    for (Class clazz : classes)
      add(clazz.getProtectionDomain().getCodeSource().getLocation());
  }

  public <T> T newObject(ObjectConstructionRules rules) {
    rules.validate(classes);
    Class<T> castTo = rules.targetType;
    return JclUtils.cast(createObject(rules), castTo, castTo.getClassLoader());
  }

  private Object createObject(ObjectConstructionRules rules) {
    String className = rules.implementingType.getName();
    String factoryMethod = rules.factoryMethod;
    Object[] arguments = rules.arguments;
    Class[] argumentTypes = rules.argumentTypes;
    if (factoryMethod == null) {
      if (argumentTypes == null)
        return OBJECT_FACTORY.create(this, className, arguments);
      else
        return OBJECT_FACTORY.create(this, className, arguments, argumentTypes);
    } else {
      if (argumentTypes == null)
        return OBJECT_FACTORY.create(this, className, factoryMethod, arguments);
      else
        return OBJECT_FACTORY.create(this, className, factoryMethod, arguments, argumentTypes);
    }
  }

  public static class ObjectConstructionRules {
    private Class targetType;
    private Class implementingType;
    private String factoryMethod;
    private Object[] arguments;
    private Class[] argumentTypes;

    private ObjectConstructionRules(Class targetType) {
      this.targetType = targetType;
    }

    public static ObjectConstructionRules forTargetType(Class targetType) {
      return new ObjectConstructionRules(targetType);
    }

    public ObjectConstructionRules implementingType(Class implementingType) {
      this.implementingType = implementingType;
      return this;
    }

    public ObjectConstructionRules factoryMethod(String factoryMethod) {
      this.factoryMethod = factoryMethod;
      return this;
    }

    public ObjectConstructionRules arguments(Object... arguments) {
      this.arguments = arguments;
      return this;
    }

    public ObjectConstructionRules argumentTypes(Class... argumentTypes) {
      this.argumentTypes = argumentTypes;
      return this;
    }

    private void validate(List<Class> classes) {
      if (implementingType == null)
        implementingType = targetType;
      if (!classes.contains(implementingType))
        throw new IllegalArgumentException(
          "Class " + implementingType.getName() + " is not protected by this thread-safe classloader"
        );
    }
  }
}

Probé mi concepto con varias pruebas de unit e integration , entre ellas una que muestra cómo reproducir y resolver el problema veraPDF .

Ahora, así es como se ve tu código cuando usas mi cargador de clases especial:

Clase VeraPDFValidator :

Solo estamos agregando un static ThreadLocal<ThreadSafeClassLoader> a nuestra clase, diciéndole qué clases / bibliotecas deben incluirse en el nuevo cargador de clases (mencionar una clase por biblioteca es suficiente, luego mi herramienta identifica la biblioteca automáticamente).

Luego, a través de threadSafeClassLoader.get().newObject(forTargetType(VeraPDFValidatorHelper.class)) creamos una instancia de nuestra clase auxiliar dentro del cargador de clases seguro de subprocesos y creamos un objeto proxy para que podamos llamarla desde afuera.

Por cierto, static boolean threadSafeMode solo existe para cambiar entre el uso antiguo (inseguro) y nuevo (seguro para subprocesos) de veraPDF para que el problema original sea reproducible para el caso de prueba de integración negativa.

package de.scrum_master.app;

import de.scrum_master.thread_safe.ThreadSafeClassLoader;
import org.verapdf.core.*;
import org.verapdf.pdfa.*;

import javax.xml.bind.JAXBException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Function;

import static de.scrum_master.thread_safe.ThreadSafeClassLoader.ObjectConstructionRules.forTargetType;

public class VeraPDFValidator implements Function<InputStream, byte[]> {
  public static boolean threadSafeMode = true;

  private static ThreadLocal<ThreadSafeClassLoader> threadSafeClassLoader =
    ThreadSafeClassLoader.create(           // Add one class per artifact for thread-safe classloader:
      VeraPDFValidatorHelper.class,         //   - our own helper class
      PDFAParser.class,                     //   - veraPDF core
      VeraGreenfieldFoundryProvider.class   //   - veraPDF validation-model
    );

  private String flavorId;
  private Boolean prettyXml;

  public VeraPDFValidator(String flavorId, Boolean prettyXml)
    throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
    this.flavorId = flavorId;
    this.prettyXml = prettyXml;
  }

  @Override
  public byte[] apply(InputStream inputStream) {
    try {
      VeraPDFValidatorHelper validatorHelper = threadSafeMode
        ? threadSafeClassLoader.get().newObject(forTargetType(VeraPDFValidatorHelper.class))
        : new VeraPDFValidatorHelper();
      return validatorHelper.validatePDF(inputStream, flavorId, prettyXml);
    } catch (ModelParsingException | ValidationException | JAXBException | EncryptedPdfException e) {
      throw new RuntimeException("invoking veraPDF validation", e);
    }
  }
}

Clase VeraPDFValidatorHelper :

En esta clase aislamos todos los accesos a la biblioteca rota. Nada especial aquí, solo el código copiado de la pregunta del OP. Todo lo que se hace aquí sucede dentro del cargador de clases seguro para subprocesos.

package de.scrum_master.app;

import org.verapdf.core.*;
import org.verapdf.pdfa.*;
import org.verapdf.pdfa.flavours.PDFAFlavour;
import org.verapdf.pdfa.results.ValidationResult;

import javax.xml.bind.JAXBException;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

public class VeraPDFValidatorHelper {
  public byte[] validatePDF(InputStream inputStream, String flavorId, Boolean prettyXml)
    throws ModelParsingException, ValidationException, JAXBException, EncryptedPdfException
  {
    VeraGreenfieldFoundryProvider.initialise();
    PDFAFlavour flavour = PDFAFlavour.byFlavourId(flavorId);
    PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false);
    PDFAParser loader = Foundries.defaultInstance().createParser(inputStream, flavour);
    ValidationResult result = validator.validate(loader);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    XmlSerialiser.toXml(result, baos, prettyXml, false);
    return baos.toByteArray();
  }
}




java hibernate jpa