-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: JPA - Delete_Orphan und not nullable...
PostPosted: Tue Dec 22, 2009 1:02 pm 
Newbie

Joined: Tue Dec 14, 2004 3:17 pm
Posts: 1
Hallo Forum,

obwohl ich hier noch nie was gepostet habe bin ich jetzt schon seit einigen Jahren Hibernate-User.

Im Moment bin ich dabei bei einem Projekt etwas "aufzuraeumen". Dabei moechte ich auch saubere Cascade-Definitionen machen, und bin dabei auf seltsame Probleme gestossen. Das ganze hab ich mal in ein kleines Beispiel gepackt:

Hier mal die Eckdaten:

Hibernate version:
Hibernate Core 3.3.2 GA, Hibernate Annotation 3.4.0 GA, Hibernate EntityManager 3.4.0 GA, ej3b-persistance 1.0.2GA

Mappings / Pojo's:

ParentPojo:
Code:
package org.tests.deleteOrphan;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.annotations.Cascade;

@Entity
@Table(name="TST_DO_PARENT")
@SequenceGenerator(name="PK",sequenceName="SQ_TST_DO_PARENT_ID",allocationSize=1)
public class ParentPojo {
   @Column(name = "id", nullable=false)
   @Id  @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PK")   // Firebird
   //@Id  @GeneratedValue // MySQL Style
   private Long id;
   
   public Long getId() {
      return id;
   }
   public void setId(Long id) {
      this.id = id;
   }

   @OneToMany(fetch=FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
   @JoinColumn(name="PARENT_ID")
   @Cascade( { org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
   protected List<ClientPojo>      clients;
   
   @Column(name="S_VALUE")
   protected String value;
   
   public List<ClientPojo> getClients() {
      return clients;
   }
   public void setClients(List<ClientPojo> clients) {
      this.clients = clients;
   }
   
   public String getValue() {
      return value;
   }
   public void setValue(String value) {
      this.value = value;
   }
}


ClientPojo:
Code:
package org.tests.deleteOrphan;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import org.hibernate.annotations.ForeignKey;

/**
* @author marvin
*/
@Entity
@Table(name="TST_DO_CLIENT")
@SequenceGenerator(name="PK",sequenceName="SQ_TST_DO_CLIENT_ID",allocationSize=1)
public class ClientPojo extends SimpleDatabaseObject {

   private static final long serialVersionUID = 20091222001L;
   @Column(name = "id", nullable=false)
   @Id  @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PK")   // Firebird style
   //@Id  @GeneratedValue // MySQL Style
   private Long id;
   
   public Long getId() {
      return id;
   }
   public void setId(Long id) {
      this.id = id;
   }

   @ManyToOne(fetch=FetchType.LAZY, optional=false)
   @JoinColumn(name="PARENT_ID", nullable=false)
   @ForeignKey(name = "FK_PA_ROLEGRANT_ASSIGNEE_ID")
   private ParentPojo         parent;

   @Column(name="S_VALUE")
   private String value;
   
   public void setParent(ParentPojo parent) {
      this.parent = parent;
   }
   
   public ParentPojo getParent() {
      return parent;
   }
   public String getValue() {
      return value;
   }
   public void setValue(String value) {
      this.value = value;
   }
   
}


Also im grossen und ganzen nix grossartiges. Es ist halt JPA-Mapping mit dem entsprechenden "Hibernate DELETE_ORPHAN" hack der an diversen Stellen dokumentiert ist.

Hier mein Testcase:

Code:
    @Test
    public void testPojoDeleteOrphaned() throws EntityNotFoundException {
       ParentPojo pp = new ParentPojo();
       pp.setClients(new ArrayList<ClientPojo>());
       pp.setValue("Parent");
       
       ClientPojo cp = new ClientPojo();
       cp.setValue("Client");
       cp.setParent(pp);
       pp.getClients().add(cp);
       
       parentPojoDAO.persist(pp);
       
       parentPojoDAO.remove(pp);
       
    }


Der Test baut eine einfache Struktur auf: ein Parent, das ein Child in der Liste hat. Und das Child auf das Parent verlinkt.

Beim persist() auf das Parent wird das Child auch sauber mit angelegt und gespeichert.

Allerdings beim remove() gibt es diesen Fehler:

Und hier mein "Fehler"-Stacktrace:

Code:
Dec 22, 2009 4:58:42 PM org.hibernate.util.JDBCExceptionReporter logExceptions
WARNING: SQL Error: 335544347, SQLState: HY000
Dec 22, 2009 4:58:42 PM org.hibernate.util.JDBCExceptionReporter logExceptions
SEVERE: GDS Exception. 335544347. validation error for column PARENT_ID, value "*** null ***"
Dec 22, 2009 4:58:42 PM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: could not delete collection: [org.tests.deleteOrphan.ParentPojo.clients#16]
   at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126)
   at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114)
   at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
   at org.hibernate.persister.collection.AbstractCollectionPersister.remove(AbstractCollectionPersister.java:1093)
   at org.hibernate.action.CollectionRemoveAction.execute(CollectionRemoveAction.java:107)
   at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
   at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
   at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:169)
   at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
   at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
   at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
   at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:366)
   at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
   at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
   at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:456)
   at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:732)
   at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:701)
   at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:321)
   at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:116)
   at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
   at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:635)
   at org.tests.deleteOrphan.ParentPojoDAO$$EnhancerByCGLIB$$55fa4a6.remove(<generated>)
   at org.tests.PartnerTest.testPojoDeleteOrphaned(PartnerTest.java:81)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
   at java.lang.reflect.Method.invoke(Method.java:597)
   at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
   at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
   at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
   at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
   at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
   at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
   at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
   at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
   at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
   at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
   at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
   at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
   at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
   at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
   at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
   at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
   at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
   at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
   at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
   at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
   at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.firebirdsql.jdbc.FBSQLException: GDS Exception. 335544347. validation error for column PARENT_ID, value "*** null ***"
   at org.firebirdsql.jdbc.AbstractPreparedStatement.internalExecute(AbstractPreparedStatement.java:712)
   at org.firebirdsql.jdbc.AbstractPreparedStatement.executeUpdate(AbstractPreparedStatement.java:172)
   at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:46)
   at org.hibernate.persister.collection.AbstractCollectionPersister.remove(AbstractCollectionPersister.java:1070)
   ... 47 more


Meine Findings:

Ich hab schon probiert das Problem zu isolieren. Der Stacktrace wird bei folgendem Statement ausgeloest:

Code:
Hibernate: update TST_DO_CLIENT set PARENT_ID=null where PARENT_ID=?


Das verstoest natuerlich ganz klar gegen die annotations und das angelegte Datenbank-Constraint:
Code:
   @ManyToOne(fetch=FetchType.LAZY, optional=false)
   @JoinColumn(name="PARENT_ID", nullable=false)


Ich hab dann mal meinen Debugger bemueht um die Quelle fuer dieses SQL zu finden:
org.hibernate.persister.collection.OneToManyPersister#generateDeleteRowString()
Das scheint also 'works as designed' zu sein. Entferne ich das Datenbank-Constraint, funktioniert der DELETE_ORPHAN. Es ist auch so, das nach diesem "set PARENT_ID=null" ein drop ausgefuehrt wird.

Ich hab jetzt in der OneToManyPersister-Klasse etwas gewuehlt, und bin noch auf die Methode isRowDeleteEnabled() gekommen. Diese ueberprueft, ob "keyIsNullable" gesetzt ist.
Und dies scheint die Ursache des Uebels zu sein:
keyIsNullable ist true, obwohl nullable=false bzw. optional=false in den Annotations gesetzt ist. Waere naemlich keyIsNullable=false, wuerde dieser seltsame Update nicht ausgefuehrt und der (spaetere) Drop wuerde mir den Eintrag sauber entfernen.

Zur Vollstaendigkeit die generierten Statements (ohne constraint auf der DB):
persist():
Code:
Hibernate: select gen_id( SQ_TST_DO_PARENT_ID, 1 ) from RDB$DATABASE
Hibernate: select gen_id( SQ_TST_DO_CLIENT_ID, 1 ) from RDB$DATABASE
Hibernate: insert into TST_DO_PARENT (S_VALUE, id) values (?, ?)
Hibernate: insert into TST_DO_CLIENT (PARENT_ID, S_VALUE, id) values (?, ?, ?)
Hibernate: update TST_DO_CLIENT set PARENT_ID=? where id=?


remove():
Code:
Hibernate: select parentpojo0_.id as id1_1_, parentpojo0_.S_VALUE as S2_1_1_, clients1_.PARENT_ID as PARENT3_3_, clients1_.id as id3_, clients1_.id as id0_0_, clients1_.PARENT_ID as PARENT3_0_0_, clients1_.S_VALUE as S2_0_0_ from TST_DO_PARENT parentpojo0_ left outer join TST_DO_CLIENT clients1_ on parentpojo0_.id=clients1_.PARENT_ID where parentpojo0_.id=?
Hibernate: update TST_DO_CLIENT set PARENT_ID=null where PARENT_ID=?
Hibernate: delete from TST_DO_CLIENT where id=?
Hibernate: delete from TST_DO_PARENT where id=?


Nun steh ich da und weiss nicht weiter....

Was kann ich machen? Die Constraints wuerde ich nur sehr ungerne entfernen...

Danke & Gruesse,
marvin


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.