xnopre
xnopre

Reputation: 603

JPA Multiple Embedded fields with prefix?

With JPA annoations, I want to reuse same embedded object like this :

@Entity
public class User {
    @Embedded
    public Address homeAddress;

    @Embedded
    public Address workAddress;
}

@Embeddable
public class Address {
    public String code;
    public String city;
    ...
} 

I can specify SQL column names with @AttributeOverrides, @AttributeOverride and @Column, but it's verbos. Is it possible to specify just a prefix to add to each column for homeAddress and workAddress ?

Thanks,

Xavier

Upvotes: 39

Views: 20791

Answers (4)

YDZOGODOQ
YDZOGODOQ

Reputation: 111

@AttributeOverride defeats purpose of multiple same Embeddable instances (contactAddress, billingAddress, balanceFrom, balanceTo, coordinates, gpsLocation etc..).
For each Embedded object you have to write down AttributeOverwrites for all fields of embeddable object. Then it is then simpler just not to use @AttributeOverride and @Embedded and directly write down the fields with the prefixes - much lesser boilercode to maintain.

My current workaround: I am using my custom naming strategy by extending ImplicitNamingStrategyJpaCompliantImpl in hibernate 6.2.6 and overwriting transformAttributePath

allProperties.put("hibernate.implicit_naming_strategy", CustomImplicitNamingStrategy.class.getName());

@Override
    protected String transformAttributePath(AttributePath attributePath) {
        String fullPath = attributePath.getFullPath();
        String withprefixMarker = "prefixed_";
        String result = attributePath.getProperty();
        if (fullPath.contains(withprefixMarker)) {
            List<String> prefixes = new ArrayList<>();
            String[] split = fullPath.split("\\.");
            for (int i = 0; i < split.length; i++) {
                if (split[i].startsWith(withprefixMarker)) {
                    String prefxFieldName = split[i].replace(withprefixMarker, "");
                    prefixes.add(prefxFieldName);
                }
            }
            result = String.join("_", prefixes) + "_" + result.replace(withprefixMarker, "");
        }

        return result;
    }

Now I can use same @Embedded with different magic prefix string ("prefixed_") and also nest it :

The table:

create table person
( ....

    mail varchar(20),
    street varchar(20),
    city varchar(20),
    previous_street varchar(20),
    previous_city varchar(20),
    
    billing_mail varchar(20),
    billing_street varchar(20),
    billing_city varchar(20),
    billing_previous_street varchar(20),
    billing_previous_city varchar(20),
    
    shipping_mail varchar(20),
    shipping_street varchar(20),
    shipping_city varchar(20),
    shipping_previous_street varchar(20),
    shipping_previous_city varchar(20),

);

JPA classes


@Embeddable
@ToString
@Getter
@Setter
public class Contact implements Serializable {
   String mail

    @Embedded
    Address address = new Address();

    @Embedded
    Address prefixed_previous = new Address();
}


@Embeddable
@ToString
@Getter
@Setter
public class Address implements Serializable {
   String street;
   String city;
   //lots of more fields.... here
}


@Entity
@Table(name = "Person")
public class PersonDb {
  ....
    @Embedded
    Contact u1 = new Contact();

    @Embedded
    Contact prefixed_billing = new Contact();

    @Embedded
    Contact prefixed_shipping = new Contact();
}

Open Issue in GitHub since 2012 ("add a prefixing mechanism for @Embedded ): https://github.com/jakartaee/persistence/issues/23

Upvotes: 1

k3b
k3b

Reputation: 14755

In 2022 (after 9 Years) @zaw-than-oo -s answer is still valid :-( .

This "Duplicate" answer is for reference if sombody wants to improve jpa-embedded.

Here is a working crossplatform example based on @zaw-than-oo -s answer with verbose java-jpa and easy to use android-room

@androidx.room.Entity
@javax.persistence.Entity
@javax.persistence.Inheritance(strategy = javax.persistence.InheritanceType.SINGLE_TABLE)
public class AppHardware {
    @javax.persistence.Id
    @javax.persistence.GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
    @androidx.room.PrimaryKey(autoGenerate = true)
    private int id;

    // max is embedded without column renaming
    @androidx.room.Embedded
    @javax.persistence.Embedded
    private ProfileCommon max;

    // min is embedded with column renaming
    @androidx.room.Embedded(prefix = "min")
    @javax.persistence.Embedded
    @javax.persistence.AttributeOverrides({
        // Verbose: every persisted property in ProfileCommon needs an entry
        // see https://stackoverflow.com/questions/12912063/jpa-multiple-embedded-fields-with-prefix
        @AttributeOverride(name = "added", column = @Column(name = "minadded")),
        @AttributeOverride(name = "apkName", column = @Column(name = "minapkName")),
        @AttributeOverride(name = "versionCode", column = @Column(name = "minversionCode")),
        @AttributeOverride(name = "versionName", column = @Column(name = "minversionName")),
        @AttributeOverride(name = "size", column = @Column(name = "minsize"))
        
    })
    private ProfileCommon min;

    // getter and setter onmitted
}

@javax.persistence.MappedSuperclass
public class ProfileCommon  {
    private long added; // date
    private String apkName;
    private long versionCode;
    private String versionName;
    private long size;

// getter and setter onmitted
}

Upvotes: 1

Mahes
Mahes

Reputation: 4135

adding this works for me (i'm using hibernate as JPA provider though)

<property name="hibernate.implicit_naming_strategy" value="org.hibernate.boot.model.naming.ImplicitNamingStrategyComponentPathImpl" />

Upvotes: 7

Zaw Than oo
Zaw Than oo

Reputation: 9935

If you would like to use multiple same Embedded class. You have to do @AttributeOverrides for all columns. Try as below;

Reference JPA AttributeOverrides

@Embeddable
public class Address {
    private String state;
    @Column(name = "zip_code")
    private String zip;
}

@Entity(name = "Employee")
public class Employee implements Serializable {
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "state", column = @Column(name = "province_1")),                       
        @AttributeOverride(name = "zip", column = @Column(name = "postal_code_2"))
    })
    private Address address_1;  

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "state", column = @Column(name = "province_2")),                       
        @AttributeOverride(name = "zip", column = @Column(name = "postal_code_2"))
    })
    private Address address_2;  

}   

My suggestion, if there are one or more Embedded value in your Entity. Try to use @CollectionTable.

@CollectionTable(name = "EMPLOYEE_ADDRESS", joinColumns = @JoinColumn(name = "ADDRESS_ID"))
private List<Address> addressList;

Reference JPA CollectionTable

Upvotes: 16

Related Questions