JPA Wiele osadzonych pól

84

Czy klasa jednostki JPA może zawierać dwa @Embeddedpola embedded ( )? Przykładem może być:

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

    @Embedded
    public Address work;
}

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

W tym przypadku Personmoże zawierać dwie Addressinstancje - dom i praca. Używam JPA z implementacją Hibernate. Kiedy generuję schemat za pomocą narzędzi Hibernate Tools, osadza tylko jeden Address. Chciałbym mieć dwie osadzone Addressinstancje, każda z wyróżnionymi nazwami kolumn lub poprzedzonymi jakimś prefiksem (np. Dom i praca). Wiem o tym @AttributeOverrides, ale wymaga to indywidualnego nadpisania każdego atrybutu. Może to być kłopotliwe, jeśli osadzony obiekt ( Address) staje się duży, ponieważ każda kolumna musi być indywidualnie nadpisana.

Steve Kuo
źródło

Odpowiedzi:

28

Jeśli chcesz mieć ten sam typ obiektu osadzalnego dwa razy w tej samej encji, domyślna nazwa kolumny nie zadziała: co najmniej jedna z kolumn będzie musiała być jawna. Hibernate wykracza poza specyfikację EJB3 i umożliwia ulepszenie mechanizmu domyślnego poprzez NamingStrategy. DefaultComponentSafeNamingStrategy to niewielkie ulepszenie w stosunku do domyślnego EJB3NamingStrategy, które umożliwia ustawianie wartości domyślnych osadzonych obiektów, nawet jeśli są używane dwukrotnie w tej samej encji.

Z dokumentu Hibernate Annotations: http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e714

Loki
źródło
89

Ogólny sposób JPA to zrobić za pomocą @AttributeOverride. To powinno działać zarówno w EclipseLink, jak i 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
źródło
9
Zwróć uwagę, że name="street"odnosi się to do nazwy właściwości, a nie do nazwy kolumny.
Bart Swennenhuis
Czy jest to uważane za „niezgrabne”, ponieważ wymaga od programisty Person znajomości intymnych informacji o klasie Adres (np. Nazwy pola zawierającego nazwę ulicy)?
mbmast
wow, więc muszę to wszystko powtórzyć, używając adnotacji. wtf. równie dobrze mógłbym samodzielnie zadeklarować całą osadzoną klasę za pomocą String homeStreet; Zamiast tego string workStreeet, prawdopodobnie bardziej deterministyczny.
mmm
6

W przypadku korzystania z łącza Eclipse, alternatywa dla korzystania z AttributeOverrides go do korzystania z SessionCustomizer. To rozwiązuje problem dla wszystkich podmiotów za jednym razem:

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
źródło
+1 Byłoby miło mieć to jako DescriptorCustomizer, aby móc kontrolować to dla każdej klasy, ale nie znalazłem sposobu, aby uzyskać dostęp do ClassDescriptor klasy osadzonej z poziomu DescriptorCustomizer klasy hosta.
oulenz
1
Alternatywą, której teraz używam, jest sprawdzenie classDescriptor.getJavaClass()w SessionCustomizer listy klas, na które chcę, aby miało to wpłynąć.
oulenz