88

Is it possible for a JPA entity class to contain two embedded (@Embedded) fields? An example would be:

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

    @Embedded
    public Address work;
}

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

In this case a Person can contain two Address instances - home and work. I'm using JPA with Hibernate's implementation. When I generate the schema using Hibernate Tools, it only embeds one Address. What I'd like is two embedded Address instances, each with its column names distinguished or pre-pended with some prefix (such as home and work). I know of @AttributeOverrides, but this requires that each attribute be individually overridden. This can get cumbersome if the embedded object (Address) gets big as each column needs to be individually overridden.

sblundy
  • 60,628
  • 22
  • 121
  • 123
Steve Kuo
  • 61,876
  • 75
  • 195
  • 257

4 Answers4

89

The generic JPA way to do it is with @AttributeOverride. This should work in both EclipseLink and 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;
    ...
  }
}
Anoop
  • 23,044
  • 10
  • 62
  • 76
Philihp Busby
  • 4,389
  • 4
  • 23
  • 19
  • 10
    Note that `name="street"` refers to the name of the property, not the column name. – Bart Swennenhuis Aug 17 '15 at 14:41
  • 1
    Is this considered "clunky," as it requires the developer of Person to know intimate things about the class Address (like the name of the field containing the street name)? – mbmast Feb 02 '20 at 01:00
  • 1
    wow, so i have to repeat it all using annotations. wtf. i could just as well declare the entire embedded class myself manually using String homeStreet; String workStreeet instead, probably more deterministic. – mjs Sep 29 '20 at 19:11
  • @mmm Before you "wtf" you might take the time to get aware of the different contexts you're in. Why would you want to create a dedicated FooAddress class for every foo you might have in the referring entity? – Amadán May 28 '21 at 06:43
  • 1
    @Amadán for me, to avoid redundancy. Having to redeclare the rules of Adress related stuff over and over again. Unfortunately Hibernate does not offer great ways to do that. – mjs May 30 '21 at 09:47
  • @mbmast Your team may find it intuitive to have a 1:1 mapping of database table to Entity classes. Your DBAs already decided that homeStreet should be denormalized and on the Person table. – Philihp Busby May 31 '21 at 19:10
  • @mmm TIMTOWDI, there are often cleaner solutions in a greenfields environment. OP may have had lots of existing legacy code using person.home.street that they can't change. – Philihp Busby May 31 '21 at 19:10
29

If you want to have the same embeddable object type twice in the same entity, the column name defaulting will not work: at least one of the columns will have to be explicit. Hibernate goes beyond the EJB3 spec and allows you to enhance the defaulting mechanism through the NamingStrategy. DefaultComponentSafeNamingStrategy is a small improvement over the default EJB3NamingStrategy that allows embedded objects to be defaulted even if used twice in the same entity.

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

Arjan
  • 22,808
  • 11
  • 61
  • 71
Loki
  • 29,950
  • 9
  • 48
  • 62
6

When using Eclipse Link, an alternative to using AttributeOverrides it to use a SessionCustomizer. This solves the issue for all entities in one go:

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
  • 2,434
  • 1
  • 21
  • 30
  • +1 Would be nice to have this as a DescriptorCustomizer, to be able to control this per class, but I haven't found a way to access the ClassDescriptor of the embedded class from within the DescriptorCustomizer of the host class. – oulenz Sep 19 '16 at 13:47
  • 1
    The alternative I'm using now is to check `classDescriptor.getJavaClass()` in the SessionCustomizer against a list of classes I want it to affect. – oulenz Sep 19 '16 at 14:30
2

In case you are using hibernate you can also use a different naming scheme which adds unique prefixes to columns for identical embedded fields. See Automatically Add a Prefix to Column Names for @Embeddable Classes

Community
  • 1
  • 1
Arjan Mels
  • 279
  • 1
  • 3
  • 11