Campos integrados múltiples JPA

84

¿Es posible que una clase de entidad JPA contenga dos @Embeddedcampos incrustados ( )? Un ejemplo sería:

@Entity
public class Person {
    @Embedded
    public Address home;

    @Embedded
    public Address work;
}

public class Address {
    public String street;
    ...
}

En este caso, a Personpuede contener dos Addressinstancias: hogar y trabajo. Estoy usando JPA con la implementación de Hibernate. Cuando genero el esquema usando Hibernate Tools, solo incrusta uno Address. Lo que me gustaría son dos Addressinstancias incrustadas , cada una con sus nombres de columna distinguidos o precedidos por algún prefijo (como casa y trabajo). Lo sé @AttributeOverrides, pero esto requiere que cada atributo sea anulado individualmente. Esto puede volverse engorroso si el objeto incrustado ( Address) aumenta de tamaño, ya que cada columna debe anularse individualmente.

Steve Kuo
fuente

Respuestas:

28

Si desea tener el mismo tipo de objeto incrustable dos veces en la misma entidad, el nombre de columna predeterminado no funcionará: al menos una de las columnas tendrá que ser explícita. Hibernate va más allá de la especificación EJB3 y le permite mejorar el mecanismo de incumplimiento a través de NamingStrategy. DefaultComponentSafeNamingStrategy es una pequeña mejora sobre la EJB3NamingStrategy predeterminada que permite que los objetos incrustados sean predeterminados incluso si se usan dos veces en la misma entidad.

Del documento de anotaciones de Hibernate: http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e714

Loki
fuente
89

La forma genérica de JPA de hacerlo es con @AttributeOverride. Esto debería funcionar tanto en EclipseLink como en Hibernate.

@Entity 
public class Person {
  @AttributeOverrides({
    @AttributeOverride(name="street",column=@Column(name="homeStreet")),
    ...
  })
  @Embedded public Address home;

  @AttributeOverrides({
    @AttributeOverride(name="street",column=@Column(name="workStreet")),
    ...
  })
  @Embedded public Address work;
  }

  @Embeddable public class Address {
    @Basic public String street;
    ...
  }
}
Philihp Busby
fuente
9
Tenga en cuenta que se name="street"refiere al nombre de la propiedad, no al nombre de la columna.
Bart Swennenhuis
¿Se considera esto "torpe", ya que requiere que el desarrollador de Person sepa cosas íntimas sobre la clase Dirección (como el nombre del campo que contiene el nombre de la calle)?
mbmast
wow, así que tengo que repetirlo todo usando anotaciones. wtf. también podría declarar toda la clase insertada manualmente usando String homeStreet; String workStreeet en cambio, probablemente más determinista.
mmm
6

Al usar Eclipse Link, una alternativa al uso de Attribute lo invalida para usar un SessionCustomizer. Esto resuelve el problema para todas las entidades de una sola vez:

public class EmbeddedFieldNamesSessionCustomizer implements SessionCustomizer {

@SuppressWarnings("rawtypes")
@Override
public void customize(Session session) throws Exception {
    Map<Class, ClassDescriptor> descriptors = session.getDescriptors();
    for (ClassDescriptor classDescriptor : descriptors.values()) {
        for (DatabaseMapping databaseMapping : classDescriptor.getMappings()) {
            if (databaseMapping.isAggregateObjectMapping()) {
                AggregateObjectMapping m = (AggregateObjectMapping) databaseMapping;
                Map<String, DatabaseField> mapping = m.getAggregateToSourceFields();

                ClassDescriptor refDesc = descriptors.get(m.getReferenceClass());
                for (DatabaseMapping refMapping : refDesc.getMappings()) {
                    if (refMapping.isDirectToFieldMapping()) {
                        DirectToFieldMapping refDirectMapping = (DirectToFieldMapping) refMapping;
                        String refFieldName = refDirectMapping.getField().getName();
                        if (!mapping.containsKey(refFieldName)) {
                            DatabaseField mappedField = refDirectMapping.getField().clone();
                            mappedField.setName(m.getAttributeName() + "_" + mappedField.getName());
                            mapping.put(refFieldName, mappedField);
                        }
                    }

                }
            }

        }
    }
}

}
ruediste
fuente
+1 Sería bueno tener esto como DescriptorCustomizer, para poder controlar esto por clase, pero no he encontrado una forma de acceder al ClassDescriptor de la clase incrustada desde el DescriptorCustomizer de la clase de host.
oulenz
1
La alternativa que estoy usando ahora es verificar classDescriptor.getJavaClass()en SessionCustomizer una lista de clases a las que quiero que afecte.
oulenz