8

Sto scrivendo un'applicazione in cui tutte le proprietà String devono essere localizzate, vale a dire che devono memorizzare un valore diverso per ogni Locale disponibile. Una soluzione rapida sarebbe quella di utilizzare una mappa, che può essere facilmente mappato in Hibernate, ma non è bello sul programmatore Java:Mappare una stringa localizzata in Hibernate: qualsiasi best practice?

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

pertanto implementato un oggetto LocalString che può contenere stringhe diverse per i diversi locali:

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

L'oggetto di dominio diventa

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

Come faccio migliore cartina questi oggetti con annotazioni Hibernate?

Penso che il miglior mappatura sarebbe stato fatto utilizzando LocalString come componente:

@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; 
    } 

Tutto va bene fino ad ora: il compito formica hbm2ddl crea due tabelle, una Tabella "Prodotti" e una tabella "Prodotti_localStrings" che contiene colonne chiave e valore. Tutto si rompe quando aggiungo il getter per la seconda proprietà:

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

La seconda proprietà non compare nello schema. Ho provato ad utilizzare il tag @AttributesOverride per definire nomi diversi per le due colonne, ma lo schema generato non è corretto:

@Embedded 
    @AttributeOverrides({ 
      @AttributeOverride(name="localStrings", [email protected](name="description")) 
    }) 
    public LocalString getDescription() { 
      return description; 
    } 
    @Embedded 
    @AttributeOverrides({ 
      @AttributeOverride(name="localStrings", [email protected](name="note")) 
    }) 
    public LocalString getNote() { 
      return note; 
    } 

Nello schema generato, la colonna chiave è scomparso e gli usi della chiave primaria "descrizione" , che non è corretto:

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

Un modo per risolvere questo problema?

Sarebbe meglio senza componenti incorporati, utilizzando LocalString come entità?

Eventuali disegni alternativi?

Grazie.

EDIT

ho provato con una mappatura XML e sono riuscito ad ottenere uno schema adeguato, ma inserti riuscire con una violazione di chiave primaria, perché Hibernate genera due inserti invece di uno solo

<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> 

Lo schema è

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); 

Log:

01.235.
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' 

Come si comunica che la sospensione genera solo un singolo inserto come il seguente?

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

EDIT 2011.Apr.05

Sono stato utilizzando la soluzione Map per un po '(annotato con @ElementCollection), fino a quando mi sono imbattuto in due problemi:

So che ci sono molte soluzioni alternative, come l'utilizzo di HQL invece di criteri e definire il proprio FieldBridge per prendersi cura di mappa in Lucene, ma non mi piace soluzioni alternative: lavorano fino al prossimo problema viene intorno . Così sto seguendo questo approccio:

mi definiscono un "LocalString" classe che detiene locale e il valore (il locale è in realtà il codice ISO3):

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

Poi mi definisco, per ogni proprietà voglio localizzare, una sottoclasse di LocalString, che è vuoto:

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

ora posso usare nel mio oggetto del prodotto:

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; 
    } 

Con questo approccio devo scrivere una classe vuota per ogni proprietà che ho bisogno di localizzare, che è necessaria per creare una tabella univoca per quella proprietà. Il vantaggio è che posso utilizzare criteri e ricerca senza restrizioni.

+0

Altri esperimenti rivelano che AttributeOverrides collassa sia la chiave che la colonna del valore della mappa in una singola colonna. – xtian

+0

Ho provato con una mappatura xml e sono riuscito a ottenere uno schema valido, ma lo sql generato da Hibernate memorizza ciascuna proprietà su un inserto diverso, terminando con una violazione del vincolo di chiave primaria. – xtian

+0

Onestamente, questo è molto più di quanto ho tempo di leggere e presumo che ci siano più domande incluse. Ottieni un supporto molto migliore quando puoi ridurre il problema a una singola domanda che è facile da descrivere. –

risposta

1

La mappatura xml non funziona perché sono stati mappati i tipi di valori nella stessa tabella. Quando si utilizza elemento o elemento composito, i dati vengono trattati come tipi di valore e appartengono alla classe in cui è contenuto. Richiede la propria tabella. L'id è unico nella collezione.

O map come componente:

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

O come un'entità indipendente:

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

Nel secondo caso, LocalString è un'entità. Ha bisogno di un id e della sua definizione di mappatura.

+0

Hai ragione, stavo cercando di usare lo stesso tavolo per entrambe le mappe, che all'inizio era bello, ma ora capisco che è sbagliato. – xtian

+0

Nella soluzione "entità indipendente", tutte le stringhe localizzate della mia applicazione passano alla stessa tabella LocalString. Non volevo che ciò accadesse, ma ora mi rendo conto di non averlo detto nella mia domanda. Le mie scuse per non averlo chiarito. – xtian

+0

L'argomento "una tabella vs molte tabelle" è un'altra cosa che potrebbe valere la pena di essere affrontata. – xtian