15

国際化への単純なアプローチのJPA実装を取得しようとしています。複数のテーブルの複数のフィールドで参照できる翻訳済み文字列のテーブルが必要です。そのため、すべてのテーブルに出現するすべてのテキストは、翻訳された文字列テーブルへの参照に置き換えられます。言語 ID と組み合わせると、その特定のフィールドの翻訳済み文字列テーブルに一意の行が作成されます。たとえば、次のようにエンティティ Course と Module を持つスキーマを考えてみましょう:-

コース int course_id、int name、int description

モジュール int module_id、int name

course.name、course.description、および module.name はすべて、翻訳された文字列テーブルの id フィールドを参照しています:-

TranslatedString int id、文字列 lang、文字列コンテンツ

それはすべて簡単に思えます。国際化できるすべての文字列に対して 1 つのテーブルを取得し、そのテーブルを他のすべてのテーブルで使用します。

eclipselink 2.4を使用して、JPAでこれを行うにはどうすればよいですか?

埋め込まれた ElementCollection を見てきましたが、これは... JPA 2.0: マップのマッピング- 翻訳された文字列テーブルを所有テーブルの pk に関連付けているように見えます。これは、エンティティごとに 1 つの翻訳可能な文字列フィールドしか持てないことを意味します (翻訳可能な文字列テーブルに新しい結合列を追加しない限り、これはポイントを無効にし、私がやろうとしていることの反対です)。これがエンティティ間でどのように機能するかについても明確ではありません。おそらく、各エンティティの ID はデータベース全体のシーケンスを使用して、翻訳可能な文字列テーブルの一意性を確保する必要があります。

ところで、そのリンクに示されている例を試してみましたが、うまくいきませんでした-エンティティにlocalizedStringマップが追加されるとすぐに、それを永続化するとクライアント側が爆撃されましたが、サーバー側に明らかなエラーはなく、何もありませんでしたDBに永続化:S

私はこれまで約9時間家々を回っていましたが、上のリンクと同じことをしようとしているように見えるこのInternationalization with Hibernateを見てきました(テーブルの定義がなければ、彼が何を達成したかを理解するのは難しいです)。この時点で、どんな助けも感謝して達成されます...

編集1-以下のAMS anwserについて、それが実際に問題に対処しているかどうかはわかりません。彼の例では、説明テキストの保存を他のプロセスに任せています。このタイプのアプローチのアイデアは、エンティティ オブジェクトがテキストとロケールを取得し、これが (どういうわけか!) 翻訳可能な文字列テーブルになるというものです。私が提供した最初のリンクでは、男は埋め込みマップを使用してこれを行おうとしていますが、これは正しいアプローチだと思います。しかし、彼のやり方には 2 つの問題があります。2 つ目は、機能した場合は、FK を埋め込みテーブルに保存することであり、その逆ではありません (実行できないため、それがどのように持続するかを正確に確認することはできません)。正しいアプローチは、翻訳が必要な各テキストの代わりにマップ参照で終わると思います (マップはロケール -> コンテンツです)、しかし私はできます '

4

4 に答える 4

7

わかりました、私はそれを持っていると思います。ローカライズされたエンティティへの ManyToOne 関係 (メイン エンティティのテキスト要素ごとに異なる joinColumn を使用) と、そのローカライズされたエンティティ内のマップの単純な ElementCollection を使用するだけで、質問の最初のリンクの簡略化されたバージョンが機能するようです。 . 質問とは少し異なる例をコーディングしました。エンティティ (カテゴリ) は 1 つだけで、ロケールごとに複数のエントリ (名前と説明) が必要な 2 つのテキスト要素があります。

これは、MySQL に移行する Eclipselink 2.4 に対して行われたことに注意してください。

このアプローチに関する 2 つの注意事項 - 最初のリンクでわかるように、ElementCollection を使用すると別のテーブルが強制的に作成され、翻訳可能な文字列用に 2 つのテーブルが作成されます。すべてのマップ情報を保持するもの (Localized_strings)。Localized_strings という名前は、自動/デフォルトの名前です。@CollectionTable アノテーションを付けて別の名前を使用できます。全体として、これは DB の観点からは理想的ではありませんが、世界の終わりではありません。

2 つ目は、少なくとも私の Eclipselink と MySQL の組み合わせでは、単一の (自動生成された) 列テーブルに永続化するとエラーが発生することです:( したがって、ダミー列にエンティティのデフォルト値を追加しました。これは純粋に克服するためですその問題。

import java.io.Serializable;
import java.lang.Long;
import java.lang.String;
import java.util.HashMap;
import java.util.Map;

import javax.persistence.*;


@Entity

public class Category implements Serializable {

@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="NAME_ID")
private Localised nameStrings = new Localised();

@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="DESCRIPTION_ID")
private Localised descriptionStrings = new Localised();

private static final long serialVersionUID = 1L;

public Category() {

    super();
}  

public Category(String locale, String name, String description){
    this.nameStrings.addString(locale, name);
    this.descriptionStrings.addString(locale, description);
}
public Long getId() {
    return this.id;
}

public void setId(Long id) {
    this.id = id;
}   

public String getName(String locale) {
    return this.nameStrings.getString(locale);
}

public void setName(String locale, String name) {
    this.nameStrings.addString(locale, name);
}
public String getDescription(String locale) {
    return this.descriptionStrings.getString(locale);
}

public void setDescription(String locale, String description) {
    this.descriptionStrings.addString(locale, description);
}

}




import java.util.HashMap;
import java.util.Map;

import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Localised {

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    private int dummy = 0;
    @ElementCollection
    private Map<String,String> strings = new HashMap<String, String>();

    //private String locale;    
    //private String text;

    public Localised() {}

    public Localised(Map<String, String> map) {
        this.strings = map;
    }

    public void addString(String locale, String text) {
        strings.put(locale, text);
    }

    public String getString(String locale) {
        String returnValue = strings.get(locale);
        return (returnValue != null ? returnValue : null);
    }

}

したがって、これらは次のようにテーブルを生成します:-

CREATE TABLE LOCALISED (ID INTEGER AUTO_INCREMENT NOT NULL, DUMMY INTEGER, PRIMARY KEY (ID))
CREATE TABLE CATEGORY (ID BIGINT AUTO_INCREMENT NOT NULL, DESCRIPTION_ID INTEGER, NAME_ID INTEGER, PRIMARY KEY (ID))
CREATE TABLE Localised_STRINGS (Localised_ID INTEGER, STRINGS VARCHAR(255), STRINGS_KEY VARCHAR(255))
ALTER TABLE CATEGORY ADD CONSTRAINT FK_CATEGORY_DESCRIPTION_ID FOREIGN KEY (DESCRIPTION_ID) REFERENCES LOCALISED (ID)
ALTER TABLE CATEGORY ADD CONSTRAINT FK_CATEGORY_NAME_ID FOREIGN KEY (NAME_ID) REFERENCES LOCALISED (ID)
ALTER TABLE Localised_STRINGS ADD CONSTRAINT FK_Localised_STRINGS_Localised_ID FOREIGN KEY (Localised_ID) REFERENCES LOCALISED (ID)

それをテストするメイン...

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class Main {
  static EntityManagerFactory emf = Persistence.createEntityManagerFactory("javaNetPU");
  static EntityManager em = emf.createEntityManager();

  public static void main(String[] a) throws Exception {
    em.getTransaction().begin();


    Category category = new Category();

    em.persist(category);

    category.setName("EN", "Business");
    category.setDescription("EN", "This is the business category");


    category.setName("FR", "La Business");
    category.setDescription("FR", "Ici es la Business");

    em.flush();

    System.out.println(category.getDescription("EN"));
    System.out.println(category.getName("FR"));


    Category c2 = new Category();
    em.persist(c2);

    c2.setDescription("EN", "Second Description");
    c2.setName("EN", "Second Name");

    c2.setDescription("DE", "Zwei  Description");
    c2.setName("DE", "Zwei  Name");

    em.flush();


    //em.remove(category);


    em.getTransaction().commit();
    em.close();
    emf.close();

  }
}

これにより出力が生成されます:-

This is the business category
La Business

および次のテーブル エントリ:-

Category
"ID"    "DESCRIPTION_ID"    "NAME_ID"
"1"         "1"                 "2"
"2"         "3"                 "4"

Localised
"ID"    "DUMMY"
"1"         "0"
"2"         "0"
"3"         "0"
"4"         "0"

Localised_strings

"Localised_ID"  "STRINGS"                        "STRINGS_KEY"
"1"                 "Ici es la Business"                 "FR"
"1"                 "This is the business category"      "EN"
"2"                 "La Business"                        "FR"
"2"                 "Business"                       "EN"
"3"                 "Second Description"                 "EN"
"3"                 "Zwei  Description"              "DE"
"4"                 "Second Name"                        "EN"
"4"                 "Zwei  Name"                         "DE"

em.remove のコメントを外すと、Category とそれに関連付けられている Locaized/Localized_strings エントリの両方が正しく削除されます。

すべてが将来誰かを助けることを願っています。

于 2012-11-17T18:57:19.007 に答える
1

少し遅いことはわかっていますが、次のアプローチを実装しました。

 @Entity
 public class LocalizedString extends Item implements Localizable<String>
 {
     @Column(name = "en")
     protected String en;

     @Column(name = "en_GB")
     protected String en_GB;

     @Column(name = "de")
     protected String de;

     @Column(name = "de_DE")
     protected String de_DE;

     @Column(name = "fr")
     protected String fr;

     @Column(name = "fr_FR")
     protected String fr_FR;

     @Column(name = "es")
     protected String es;

     @Column(name = "es_ES")
     protected String es_ES;

     @Column(name = "it")
     protected String it;

     @Column(name = "it_IT")
     protected String it_IT;

     @Column(name = "ja")
     protected String ja;

     @Column(name = "ja_JP")
     protected String ja_JP;
 }

エンティティにはセッターとゲッターがありません! 代わりに、Localizableインターフェイスは共通の get/set メソッドを定義します。

public class Localizable<T> {
    private final KeyValueMapping<Locale, T> values = new KeyValueMapping<>();

    private T defaultValue = null;

    /**
     * Generates a {@link Localizable} that only holds one value - for all locales.
     * This value overrides all localalized values when using
     * {@link Localizable#toString()} or {@link Localizable#get()}.
     */
    public static <T> Localizable<T> of(T value) {
        return new Localizable<>(value);
    }

    public static <T> Localizable<T> of(Locale locale, T value) {
        return new Localizable<>(locale, value);
    }

    private Localizable(T value) {
        this.defaultValue = value;
    }

    private Localizable(Locale locale, T value) {
        this.values.put(locale, value);
    }

    public Localizable() {
    }

    public void set(Locale locale, T value) {
        values.put(locale, value);
    }

    /**
     * Returns the value associated with the default locale
     * ({@link Locale#getDefault()}) or the default value, if it is set.
     */
    public T get() {
        return defaultValue != null ? defaultValue : values.get(Locale.getDefault());
    }

    public T get(Locale locale) {
        return values.get(locale);
    }

    /**
     * Returns the toString of the value for the default locale
     * ({@link Locale#getDefault()}).
     */
    @Override
    public String toString() {
        if (defaultValue != null) {
            return defaultValue.toString();
        }

        return toString(Locale.getDefault());
    }

    /**
     * Returns toString of the localized value.
     * 
     * @return null if there is no localized.
     */
    public String toString(Locale locale) {
        return values.transformValue(locale, v -> v.toString());
    }

    public Map<Locale, T> getValues() {
        return Collections.unmodifiableMap(values);
    }

    public T getDefaultValue() {
        return defaultValue;
    }

    public void setDefaultValue(T defaultValue) {
        this.defaultValue = defaultValue;
    }

}

このアプローチの大きな利点は、ローカライズ可能なエンティティが 1 つしかないことと、ローカライズされた値が列に格納されることです (ローカライズごとに 1 つのエンティティを持つのではなく)。

于 2018-07-31T18:37:53.687 に答える
-1

ここにそれを行う1つの方法があります。

データベースからすべての翻訳された文字列をキャッシュにロードして、それを MessagesCache と呼びます。 public String getMesssage(int id, int languageCode) というメソッドがあります。Google グアバの不変コレクションを使用して、これをメモリ キャッシュに格納できます。必要に応じてキャッシュをロードしたい場合は、Guava LoadingCache を使用してキャッシュ値を格納することもできます。そのようなキャッシュがあれば、このようなコードを書くことができます。

@Entity 
public Course {
    @Column("description_id")
    private int description;

    public String getDescription(int languageCode)
    { 
        return this.messagesCache(description, languageCode);
    }


    public String setDscription(int descriptionId)
    {
         this.description = descriptionId; 
    }
} 

このアプローチで私が目にする主な問題は、エンティティで参照しているロケールを知る必要があることです。説明用の正しい言語を選択するタスクは、エンティティではなく、より高いレベルで行う必要があることをお勧めしますDao や Service などの抽象化。

于 2012-11-17T01:34:50.843 に答える