Многоязычное сопоставление сущностей JPA

Используя этот пример:

CREATE TABLE IF NOT EXISTS COUNTRY (
    COUNTRY_CODE            VARCHAR(3) NOT NULL,
    DIALLING_CODE           VARCHAR(5) NOT NULL,
    CREATION_TIMESTAMP      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CREATED_BY              VARCHAR(20) NOT NULL,
    LAST_UPDATE_TIMESTAMP   TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
    LAST_UPDATED_BY         VARCHAR(20),
    DELETION_TIMESTAMP      TIMESTAMP NULL,
    DELETED_BY              VARCHAR(20),

    PRIMARY KEY(NUMERIC_CODE),
    UNIQUE(ALPHA2_CODE),
    UNIQUE(ALPHA3_CODE),
    UNIQUE(DIALLING_CODE),
    FOREIGN KEY(CREATED_BY) REFERENCES USER(USER_NAME),
    FOREIGN KEY(LAST_UPDATED_BY) REFERENCES USER(USER_NAME),
    FOREIGN KEY (DELETED_BY) REFERENCES USER(USER_NAME)
) CHARACTER SET utf8;

CREATE TABLE IF NOT EXISTS COUNTRY_NAME (
    COUNTRY_CODE            VARCHAR(3) NOT NULL,
    LANGUAGE_CODE           VARCHAR(2) NOT NULL,
    NAME                    VARCHAR(20) NOT NULL,
    CREATION_TIMESTAMP      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CREATED_BY              VARCHAR(20) NOT NULL,
    LAST_UPDATE_TIMESTAMP   TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
    LAST_UPDATED_BY         VARCHAR(20),

    PRIMARY KEY (COUNTRY_CODE, LANGUAGE_CODE),
    FOREIGN KEY (COUNTRY_CODE) REFERENCES COUNTRY (COUNTRY_CODE),
    FOREIGN KEY (LANGUAGE_CODE) REFERENCES LANGUAGE (ALPHA2_CODE),
    FOREIGN KEY (CREATED_BY) REFERENCES USER(USER_NAME),
    FOREIGN KEY (LAST_UPDATED_BY) REFERENCES USER(USER_NAME)
) CHARACTER SET utf8;

Представление JPA для страны выглядит следующим образом:

@XmlRootElement
@Entity
@Table(name="COUNTRY")
@Access(AccessType.FIELD)
@Cacheable
public class Country extends AbstractSoftDeleteAuditableEntity<String> implements za.co.sindi.persistence.entity.Entity<String>, Serializable {

    private static final long serialVersionUID = -4019436514961967327L;

    @Id
    @Column(name="COUNTRY_CODE", length=3, nullable=false)
    @Size(min=3, max=3)
    @Pattern(regexp="^\\d{3}$")
    private String id;

    @Column(name="DIALLING_CODE", length=5, nullable=false)
    @Pattern(regexp="^\\+[0-9]{2,5}$")
    private String diallingCode;

    @OneToMany(cascade= CascadeType.ALL, mappedBy="country")
    private List<CountryName> names;


    /* (non-Javadoc)
     * @see za.co.sindi.entity.IDBasedEntity#getId()
     */
    public String getId() {
        // TODO Auto-generated method stub
        return id;
    }

    /* (non-Javadoc)
     * @see za.co.sindi.entity.IDBasedEntity#setId(java.io.Serializable)
     */
    public void setId(String id) {
        // TODO Auto-generated method stub
        this.id = id;
    }

    /**
     * @return the diallingCode
     */
    public String getDiallingCode() {
        return diallingCode;
    }

    /**
     * @param diallingCode the diallingCode to set
     */
    public void setDiallingCode(String diallingCode) {
        this.diallingCode = diallingCode;
    }

    /**
     * @return the names
     */
    public List<CountryName> getNames() {
        return names;
    }

    /**
     * @param names the names to set
     */
    public void setNames(List<CountryName> names) {
        this.names = names;
    }
}

В приведенной выше модели мне придется перебирать атрибут names, чтобы найти CountryName, который соответствует коду языка.

Есть ли лучший способ получить Country и CountryName на основе кода языка? Если нет, как я могу реализовать более простой подход, который позволит мне вытащить информацию Country с языковым кодом в сущность, как показано ниже:

public class Country implements Serializable {
        private static final long serialVersionUID = -4019436514961967327L;

        @Id
        @Column(name="COUNTRY_CODE", length=3, nullable=false)
        @Size(min=3, max=3)
        @Pattern(regexp="^\\d{3}$")
        private String id;

        @Column(name="DIALLING_CODE", length=5, nullable=false)
        @Pattern(regexp="^\\+[0-9]{2,5}$")
        private String diallingCode;

        private String name;

        private Language language;


        //Getters and setters will be auto generated here....
}

Таким образом, это может быть достигнуто:

Country country = countryDAO.find(countryCode);
System.out.println(country.getName());

person Buhake Sindi    schedule 18.04.2015    source источник
comment
Почему бы вам не использовать отдельный запрос только для CountryName?   -  person Predrag Maric    schedule 18.04.2015
comment
Я подумал об этом, и это означало бы, что я должен создать CountryNameDAO, так как я использую шаблон DAO повсюду.   -  person Buhake Sindi    schedule 18.04.2015
comment
Если это не то, что вы хотели бы сделать (хотя это может быть лучшим вариантом), вы можете добавить поле @Transient java.util.Map к сущности Country, которую вы бы заполнили CountryNames. Затем вы можете получить к нему доступ с помощью country.getName(countryCode). Прирост производительности может быть невелик, если только вы не кэшируете данные о стране.   -  person Predrag Maric    schedule 18.04.2015
comment
Вариант Map может быть не лучшим, так как вы хотите, чтобы он загружался отложенно. Я ищу лучший подход, поэтому, если CountryNameDAO будет лучшим вариантом, я выберу его.   -  person Buhake Sindi    schedule 18.04.2015
comment
Ленивый не будет проблемой, так как вы заполните карту при первом доступе к country.getName(countryCode).   -  person Predrag Maric    schedule 18.04.2015
comment
Как тогда мне добиться использования Map в JPA?   -  person Buhake Sindi    schedule 18.04.2015


Ответы (1)


Как я сказал в комментариях, я думаю, что лучшим решением было бы использовать отдельный запрос (и DAO) для CountryName, потому что это позволит избежать загрузки ненужных названий стран, когда вам нужно только одно для указанного countryCode.

Один из вариантов — использовать поле @Transient java.util.Map, которое будет загружено с названиями стран, с countryCode в качестве ключа карты,

@Transient
private Map<String, CountryName> countryNameMap;
...
public CountryName getName(String countryCode) {
    if (countryNameMap == null) {
        countryNameMap = loadNamesIntoMap(); // a loop with countryNameMap.put(countryName.getCountryCode(), countryName)
    }
    return countryNameMap.get(countryCode);
}

Еще один интересный вариант - использовать java.util.Map для связи между Country и CountryName.

@OneToMany(mappedBy="country")
@MapKey(name="countryCode")
private Map<String, CountryName> names;

Затем вы получите доступ к имени с помощью country.getNames().get(countryCode).

person Predrag Maric    schedule 18.04.2015
comment
Я использовал отдельный DAO, так как в CountryName нужно было добавить разные запросы, поэтому ваше первое утверждение является фактическим решением. Спасибо. - person Buhake Sindi; 04.07.2015