Отображение локализованной строки в Hibernate - есть ли лучший метод? - PullRequest
8 голосов
/ 24 января 2011

Я пишу приложение, в котором все свойства String должны быть локализованы, то есть они должны хранить разные значения для каждой доступной локали. Быстрое решение состоит в том, чтобы использовать Map, которая может быть легко отображена в Hibernate, но не подходит для Java-программиста:

public class Product {
   private Map<Locale, String> description;
   private Map<Locale, String> note;

Поэтому я реализовал объект LocalString, который может содержать разные строки для разных локалей:

public class LocalString {
   private Map<Locale, String> localStrings;

Объектом домена становится

public class Product {
   private LocalString description;
   private LocalString note;

Как лучше всего сопоставить эти объекты с аннотациями Hibernate?

Я думаю, что лучшее сопоставление будет сделано с использованием LocalString в качестве компонента:

@Embeddable
public class LocalString {
    private Map<Locale, String> localStrings;

    @ElementCollection
    public Map<Locale, String> getLocalStrings() {
       return localStrings;
    }

...

@Entity
public class Product {
   private Long id;
   private LocalString description;
   private LocalString note;

   @Embedded
   public LocalString getDescription() {
          return description;
   }

Пока все в порядке: задача hbm2ddl ant создает две таблицы: таблицу «Products» и таблицу «Products_localStrings», которые содержат столбцы ключа и значения. Все ломается, когда я добавляю геттер для второго свойства:

   @Embedded
   public LocalString getNote() {
          return note;
   }

Второе свойство не отображается в схеме. Я попытался использовать тег @AttributesOverride, чтобы определить разные имена для двух столбцов, но сгенерированная схема неверна:

   @Embedded
   @AttributeOverrides({
          @AttributeOverride(name="localStrings", column=@Column(name="description"))
   })
   public LocalString getDescription() {
          return description;
   }
   @Embedded
   @AttributeOverrides({
          @AttributeOverride(name="localStrings", column=@Column(name="note"))
   })
   public LocalString getNote() {
          return note;
   }

В сгенерированной схеме ключевой столбец исчез, а первичный ключ использует "описание", что неверно:

create table Product_localStrings (Product_id bigint not null, note varchar(255), description varchar(255), primary key (Product_id, description));

Есть ли способ это исправить?

Было бы лучше без встроенных компонентов, используя LocalString в качестве сущности?

Есть ли альтернативные варианты?

Спасибо.

EDIT

Я попытался с сопоставлением xml, и мне удалось получить правильную схему, но вставка не удалась с нарушением первичного ключа, потому что hibernate генерирует две вставки вместо одной

<hibernate-mapping>
    <class name="com.yr.babka37.demo.entity.Libro" table="LIBRO">
        <id name="id" type="java.lang.Long">
            <column name="ID" />
            <generator class="org.hibernate.id.enhanced.SequenceStyleGenerator"/>
        </id>
        <property name="titolo" type="java.lang.String">
            <column name="TITOLO" />
        </property> 
        <component name="descrizioni" class="com.yr.babka37.entity.LocalString">
        <map name="localStrings" table="libro_strings" lazy="true" access="field">
            <key>
                <column name="ID" />
            </key>
            <map-key type="java.lang.String"></map-key>
            <element type="java.lang.String">
                <column name="descrizione" />
            </element>
        </map>
        </component>
        <component name="giudizi" class="com.yr.babka37.entity.LocalString">
        <map name="localStrings" table="libro_strings" lazy="true" access="field">
            <key>
                <column name="ID" />
            </key>
            <map-key type="java.lang.String"></map-key>
            <element type="java.lang.String">
                <column name="giudizio" />
            </element>
        </map>
        </component>
    </class>
</hibernate-mapping>

Схема

create table LIBRO (ID bigint not null auto_increment, TITOLO varchar(255), primary key (ID));
create table libro_strings (ID bigint not null, descrizione varchar(255), idx varchar(255) not null, giudizio varchar(255), primary key (ID, idx));
alter table libro_strings add index FKF576CAC5BCDBA0A4 (ID), add constraint FKF576CAC5BCDBA0A4 foreign key (ID) references LIBRO (ID);

Вход

DEBUG org.hibernate.SQL - insert into libro_strings (ID, idx, descrizione) values (?, ?, ?)
DEBUG org.hibernate.SQL - insert into libro_strings (ID, idx, giudizio) values (?, ?, ?)
WARN  o.h.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
ERROR o.h.util.JDBCExceptionReporter - Duplicate entry '5-ita_ITA' for key 'PRIMARY'

Как мне сказать hibernate для создания только одной вставки, как показано ниже?

insert into libro_strings (ID, idx, descrizione, giudizio) values (?, ?, ?, ?)

РЕДАКТИРОВАТЬ в 2011 году. Апр.05

Некоторое время я использовал решение Map (помечено @ElementCollection), пока не наткнулся на две проблемы:

Я знаю, что есть много обходных путей, например, использование HQL вместо Criteria и определение собственного FieldBridge для работы с картой в Lucene, но мне не нравятся обходные пути: они работают, пока не возникнет следующая проблема. Итак, я сейчас придерживаюсь этого подхода:

Я определяю класс "LocalString", который содержит локаль и значение (локаль на самом деле является кодом ISO3):

@MappedSuperclass
public class LocalString {
private long id;
private String localeCode;
private String value;

Затем я определяю для каждого свойства, которое я хочу локализовать, подкласс LocalString, который является пустым:

@Entity
public class ProductName extends LocalString {
    // Just a placeholder to name the table
}

Теперь я могу использовать его в моем объекте Product:

public class Product {
   private Map<String, ProductName> names;

   @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
   @JoinColumn(name="Product_id")
   @MapKey(name="localeCode")
   protected Map<String, ProductName> getNames() {
    return names;
  }

При таком подходе мне нужно написать пустой класс для каждого свойства, которое мне нужно локализовать, что необходимо для создания уникальной таблицы для этого свойства. Преимущество заключается в том, что я могу использовать критерии и поиск без ограничений.

1 Ответ

1 голос
/ 05 апреля 2011

Ваше сопоставление xml не работает, потому что вы сопоставили типы значений в одной таблице.При использовании элемента или составного элемента данные обрабатываются как типы значений и относятся к классу, в котором они содержатся. Для них требуется собственная таблица.Идентификатор уникален только внутри коллекции.

Либо сопоставьте его как компонент:

    <component name="descrizioni">
      <map name="localStrings" table="descrizioni_strings" ...>
        <!-- ... -->
      </map>
    </component>
    <component name="giudizi">
      <map name="localStrings" table="giudizi_strings" ... >
        <!-- ... -->
      </map>
    </component>

Или как независимый объект:

    <many-to-one name="descrizioni" class="LocalString"/>
    <many-to-one name="giudizi" class="LocalString"/>

Во втором случае LocalStringэто сущность.Ему нужен идентификатор и собственное определение отображения.

...