2012-09-10 13 views
5

Ho controllato molti messaggi su internet e nessuno affronta il problema. Apprezzo davvero se qualcuno può aiutare! Ho una relazione figlio genitore OneToMany. Hibernate non mette in cascata il figlio dell'inserto (Livello2) quando una delle chiavi composite del bambino è la chiave esterna del genitore (Livello1). Ho specificato cascade = "all" e non importa l'inverso è vero o falso, i risultati sono gli stessi. Non c'è eccezione, e semplicemente non inserisce. Sotto la stampa mostra che Level1 è stato inserito con successo ma Level2 è stato selezionato, nessun inserto.No cascata inserire per bambino con chiave esterna sul genitore

Hibernate: INSERT INTO sst.level_1 (STATO, NOME) valori Hibernate (,??): Selezionare level2x_.LEVEL_1_ID, level2x_.TIMESEGMENT, level2x_.CATEGORY come CATEGORY4_ da sst.level_2 level2x_ dove level2x_.LEVEL_1_ID =? e level2x_.TIMESEGMENT =?

Le API sono Hibernate 4.1.6/Spring 3.1.2.

Ecco la definizione della tabella per MySQL:

CREATE TABLE level_1 
(
ID int NOT NULL PRIMARY KEY AUTO_INCREMENT, 
STATUS int, 
NAME varchar(255) 
); 

CREATE TABLE level_2 
(
LEVEL_1_ID int, 
TIMESEGMENT int, 
CATEGORY varchar(2), 
PRIMARY KEY (LEVEL_1_ID, TIMESEGMENT), 
FOREIGN KEY (LEVEL_1_ID) REFERENCES level_1(ID) ON DELETE CASCADE 
); 

Ecco il codice di prova.

public class Test { 

    public static void main(String[] args) { 
     ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); 
     doInsert(context); 
    } 

    private static void doInsert(ApplicationContext context) { 

     Level1 level1 = new Level1(); 
     Level2 level2 = new Level2(); 

     level1.setName("LEVEL 1 NAME"); 
     level1.setStatus(1); 
     level1.getLevel2s().add(level2); 

     level2.setLevel1(level1); 
     level2.setCategory("CA"); 
     level2.setId(new Level2Id(level1.getId(), 10)); 

     Level1DAO level1DAO = (Level1DAO) context.getBean("Level1DAO"); 
     level1DAO.save(level1); 
    } 
} 

Mapping per Livello1:

<hibernate-mapping> 
    <class name="com.jc.hibernate.Level1" table="level_1" catalog="sst"> 
     <id name="id" type="java.lang.Integer"> 
      <column name="ID" /> 
      <generator class="identity" /> 
     </id> 
     <property name="status" type="java.lang.Integer"> 
      <column name="STATUS" /> 
     </property> 
     <property name="name" type="java.lang.String"> 
      <column name="NAME" /> 
     </property> 
     <set name="level2s" inverse="true" cascade="all"> 
      <key> 
       <column name="LEVEL_1_ID" not-null="true" /> 
      </key> 
      <one-to-many class="com.jc.hibernate.Level2" /> 
     </set> 
    </class> 
</hibernate-mapping> 

Mapping per Livello2:

<hibernate-mapping> 
    <class name="com.jc.hibernate.Level2" table="level_2" catalog="sst"> 
     <composite-id name="id" class="com.jc.hibernate.Level2Id"> 
      <key-property name="level1Id" type="java.lang.Integer"> 
       <column name="LEVEL_1_ID" /> 
      </key-property> 
      <key-property name="timesegment" type="java.lang.Integer"> 
       <column name="TIMESEGMENT" /> 
      </key-property> 
     </composite-id> 
     <many-to-one name="level1" class="com.jc.hibernate.Level1" update="false" insert="false" fetch="select"> 
      <column name="LEVEL_1_ID" not-null="true" /> 
     </many-to-one> 
     <property name="category" type="java.lang.String"> 
      <column name="CATEGORY" length="2" /> 
     </property> 
    </class> 
</hibernate-mapping> 

risposta

1

la mappatura sembra essere corretta.

Il tuo problema sembra è la linea

level2.setId(new Level2Id(level1.getId(), 10)); 

Quando viene eseguita questa linea, il level1 ancora non ha il suo ID assegnato ad esso, così sarà nullo. Come level_1_id e il segmento sono chiave primaria composta, nessuno di questi può essere nullo. modo che avrebbe bisogno di fare prima

Integer generatedId = (Integer) Session.save(level1) 

Questo fa un inserto immediato nel database e restituisce l'identificatore. Quindi è possibile utilizzare l'identificatore per creare Lever2Id

level2.setId(new Level2Id(generatedId , 10)); 

ho provato questo su MySql con le mappature che hai fornito e il suo bel lavoro.

Se non prima salvare il primo level1, mi dà errore

Column 'LEVEL_1_ID' cannot be null 

UPDATE

Purtroppo sembra che la cascata non funziona automaticamente quando ci sono chiavi composte in child e una delle colonne in chiave composita fa riferimento alla chiave primaria del genitore. Quindi devi impostare l'id e le relazioni manualmente come mostrato sopra.

Si noti che se il bambino ha una singola colonna id e la vostra relazione è 1-1 poi Hibernate riesce a capire che la colonna primario figlio viene dal padre e può impostare automaticamente.Vedere here e here

La documentazione anche suggerire questo here

“Non è possibile utilizzare un IdentifierGenerator per generare chiavi composte. Invece l'applicazione deve assegnare i propri identificatori.”

La classe di sospensione rappresenta mappatura id composito è org.hibernate.mapping.Component (che viene utilizzato per il componente, elemento composito, identificatore composito, ecc) e il difetto la strategia di generazione dell'id è "assegnata" a meno che tu non abbia fornito un generatore di ID personalizzato che ti porta in un modo hacker creando il tuo CompositeUserType (per ID composito) e usa un generatore di Identificatore personalizzato come menzionato here. Ma questo sembra brutto e un eccessivo.

Per quanto riguarda il risparmio sia genitore e figlio e l'impostazione relazione manualmente in una transazione prima rimuovere la cascata tutto opzione dal vostro elemento set genitore o impostarlo su "none", in modo che non ci sia la persistenza transitiva da Hibernate. Salvando genitore si salverà solo genitore e non figlio.

Quindi preparare il genitore e il bambino con tutte le proprietà disponibili e impostare la relazione oggetto da entrambi i lati in questo modo (per garantire la coerenza del modello). metodo addChild

Parent parent = new Parent(); 
    parent.setStatus(1 ); 
    parent.setName("Parent"); 
    ChildId childId = new ChildId();  
    Child child = new Child(childId);  
    child.setCategory("TH");  
    parent.addChild(child); 
    child.getId().setTimesegment(10); 

del genitore aggiunge bambino per la raccolta, così come impostato è il genitore del bambino in un metodo pratico

public void addChild(Child child){ 
    if (child !=null){ 
     getChildrens().add(child); 
     child.setParent(this); 
    } 

    } 

Poi avere un metodo in DAO che prende bambino come parametro ed inoltre è transazionale, qualcosa di simile (anche se il modo migliore è quello di rendere il transazionale livello di servizio al posto dello strato DAO)

public void save(Child child){ 

    //get Session 
    Session session =....... 
    Transaction tx = session.beginTransaction(); 
    Integer generatedId = (Integer)session.save(child.getParent()); 
    ChildId childId = child.getId(); 
    childId.setChildId(generatedId);  
    session1.save(child); 
    tx1.commit(); 
    HibernateUtil.closeSession(); 
} 

a partire da ora questo è quello che posso suggerire.

+0

Grazie per la risposta. Ha senso che l'id di livello 1 sia nullo, e ciò ha causato il problema. In questo caso, come configurare la transazione per renderla atomica per i livelli 1 e 2? – John

+0

Vedere i miei aggiornamenti per salvare sia il bambino che il genitore nella transazione – Shailendra

0

Solo per l'aggiornamento. Ho rimosso l'ID composito. Utilizzare invece la chiave surrogata generata dal database nella tabella figlio e una chiave esterna nell'ID padre. Funziona con una relazione uno a molti con cascade = "all". Salvataggio genitore salva bambino in un solo passaggio. Questo non risolve la domanda originale, ma un'alternativa.

CREATE TABLE level_11 (
ID int NOT NULL PRIMARY KEY AUTO_INCREMENT, 
STATUS int, NAME varchar(255)); 

CREATE TABLE level_22 (ID int NOT NULL PRIMARY KEY AUTO_INCREMENT, 
LEVEL_11_ID int NOT NULL, 
TIMESEGMENT int NOT NULL, 
FOREIGN KEY (LEVEL_11_ID) REFERENCES level_11(ID) ON DELETE CASCADE, 
CONSTRAINT UQ_LEVEL2 UNIQUE (LEVEL_11_ID, TIMESEGMENT)); 
Problemi correlati