9

我正在编写一个应用程序,其中所有字符串属性都必须本地化,即它们必须为每个可用的区域设置存储不同的值。一个快速的解决方案是使用 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 作为实体,我会更好吗?

有什么替代设计吗?

谢谢你。

编辑

我尝试使用 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.Apr.05

我一直在使用 Map 解决方案(用@ElementCollection 注释),直到我偶然发现两个问题:

我知道有很多变通方法,例如使用 HQL 代替 Criteria 并定义您自己的 FieldBridge 来处理 Lucene 中的 Map,但我不喜欢变通方法:它们一直有效,直到下一个问题出现。所以我现在遵循这种方法:

我定义了一个包含语言环境和值的类“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;
  }

使用这种方法,我必须为需要本地化的每个属性编写一个空类,这是为该属性创建唯一表所必需的。好处是我可以不受限制地使用 Criteria 和 Search。

4

1 回答 1

1

您的 xml 映射不起作用,因为您将值类型映射到同一个表中。当使用元素或复合元素时,数据被视为值类型并属于包含它的类。它需要自己的表。id 仅在集合中是唯一的。

要么将其映射为组件:

    <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 是一个实体。它需要一个 id 和它自己的映射定义。

于 2011-04-05T13:30:53.453 回答