-->
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.  [ 11 posts ] 
Author Message
 Post subject: How to update fk column for a property-ref in bidi 1-to-1?
PostPosted: Thu Feb 26, 2004 4:32 pm 
Newbie

Joined: Wed Jan 21, 2004 5:51 pm
Posts: 14
Location: San Francisco, CA
Hello. This post addresses two problems, but the code is so similar that I thought I'd compile them into one postn. Please let me know if I should create a separate post for each.

Both problems address the way Hibernate manages unique foreign key constraints. I have two classes, Left & Right, that share a bidirectional one-to-one relationship. The relationship is enforced in the database using a unique foreign key constraint in the table for Left.

First problem: Is it possible to make Hibernate nullify foreign key columns when the target of the constraint is deleted? Currently if I create a Left & Right, associate & save them, then delete the right side of the relation -- the side that is the target of the FK constraint -- I get a FK constraint error from the database. Is it possible to instruct Hibernate to null the foreign key record that points to the Right before deleting it? See below for code, mapping, and stack trace. The method testDeleteConstrained shows the problem.

Second (more serious) problem: If I create a Left & Right, associate them , then save the Right -- the side mapped to the table that does NOT contain the FK column -- the fk column is not updated. The relationship isn't saved in the database! The fk column, rightFk, remains null after Right is saved. See the method testFkValue in the code below.

Thanks in advance.

-Dave

Hibernate Version: 2.1.1
Database: Microsoft SQL Server 2000

The code:
Code:
package com.northstar.test.example;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;

public class BidirectionalConstraintTest {
    private static SessionFactory sessionFactory;
   
    public static void main(String[] args) {
        try {
            BidirectionalConstraintTest test = new BidirectionalConstraintTest();
            test.testFkValue();
            test.testDeleteConstrained();
        } catch (HibernateException e) { e.printStackTrace(); }
    }
   
    public void testDeleteConstrained() throws HibernateException {
        BidirectionalConstraintTest bidi = new BidirectionalConstraintTest();
        Left left = new Left(); Right right = new Right();
        saveOrUpdate(left);
        right.setLeft(left);
        left.setRight(right);
        saveOrUpdate(right);
        saveOrUpdate(left);
        delete(right); // whammo! fk constraint error.
        delete(left);
    }
   
    public void testFkValue() throws HibernateException {
        BidirectionalConstraintTest bidi = new BidirectionalConstraintTest();
        Left left = new Left(); Right right = new Right();
        saveOrUpdate(left);
        right.setLeft(left);
        left.setRight(right);
        saveOrUpdate(right); // Hey, rightFk doesn't get updated!
    }
   
    public BidirectionalConstraintTest() throws HibernateException {
        Configuration cfg = new Configuration().addClass(Left.class);
        sessionFactory = cfg.buildSessionFactory();
    }
   
    public void delete(Object o) throws HibernateException {
        Transaction tx = null;
        Session sess = sessionFactory.openSession();
        try {
            tx = sess.beginTransaction();
            sess.delete(o);
            tx.commit();
        } catch (HibernateException e) {
            if (tx != null) { tx.rollback(); throw e; }
        } finally { if (sess != null) sess.close(); }
    }
   
    public void saveOrUpdate(Object o) throws HibernateException {
        Transaction tx = null;
        Session sess = sessionFactory.openSession();
        try {
            tx = sess.beginTransaction();
            sess.saveOrUpdate(o);
            tx.commit();
        } catch (HibernateException e) {
            if (tx != null) { tx.rollback(); throw e; }
        } finally { if (sess != null) sess.close(); }
    }   
}
class Left {
    private Long sys_dataStoreId;
    private Right right;
    public Right getRight() { return right; }
    public void setRight(Right right) { this.right = right; }
    public Long getSys_dataStoreId() { return sys_dataStoreId; }
    public void setSys_dataStoreId(Long sys_dataStoreId) {
        this.sys_dataStoreId = sys_dataStoreId;
    }
}
class Right {
    private Left left;
    private Long sys_dataStoreId;
    public Left getLeft() { return left; }
    public void setLeft(Left left) { this.left = left; }
    public Long getSys_dataStoreId() { return sys_dataStoreId; }
    public void setSys_dataStoreId(Long sys_dataStoreId) {
        this.sys_dataStoreId = sys_dataStoreId;
    }
}


The Mapping:
Code:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
  <class name="com.northstar.test.example.Left" table="test_Left" lazy="false" >
    <id name="sys_dataStoreId" type="long" column="sys_DataStoreId">
      <generator class="increment"/>
    </id>
    <discriminator column="sys_ObjectType" />
    <!-- one-to-one relation with the fk in the table for this entity  -->
    <many-to-one name="right"
                 class="com.northstar.test.example.Right"
                 column="rightFk"
                 unique="true"
                 cascade="none"
                 outer-join="false"
    />
  </class>   
  <class name="com.northstar.test.example.Right" table="test_Right" lazy="false" >
    <id name="sys_dataStoreId" type="long" column="sys_DataStoreId">
      <generator class="increment"/>
    </id>
    <discriminator column="sys_ObjectType" />
    <!-- bidirectional one-to-one relation with fk in the table for the opposite entity -->
    <one-to-one name="left"
                class="com.northstar.test.example.Left"
                property-ref="right"
                constrained="true"
                cascade="none"
                outer-join="false"
    />
  </class>   
</hibernate-mapping>





Exception:
Code:
Hibernate: insert into test_Right (sys_ObjectType, sys_DataStoreId) values ('com
.northstar.test.example.Right', ?)
Hibernate: update test_Left set rightFk=? where sys_DataStoreId=?
Hibernate: delete from test_Right where sys_DataStoreId=?
- SQL Error: 547, SQLState: 23000
- [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]DELETE statement conflic
ted with COLUMN REFERENCE constraint 'LeftToRight'. The conflict occurred in dat
abase 'wms_weblogic', table 'test_Left', column 'rightFk'.
- SQL Error: 3621, SQLState: HY000
- [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]The statement has been t
erminated.
- could not delete: [com.northstar.test.example.Right#2]
java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]DEL
ETE statement conflicted with COLUMN REFERENCE constraint 'LeftToRight'. The con
flict occurred in database 'wms_weblogic', table 'test_Left', column 'rightFk'.
   at com.microsoft.jdbc.base.BaseExceptions.createException(Unknown Source)
   at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
   at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processErrorToken(Unknown Source
)
   at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processReplyToken(Unknown Source
)
   at com.microsoft.jdbc.sqlserver.tds.TDSRPCRequest.processReplyToken(Unknown Sou
rce)
   at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processReply(Unknown Source)
   at com.microsoft.jdbc.sqlserver.SQLServerImplStatement.getNextResultType(Unknow
n Source)
   at com.microsoft.jdbc.base.BaseStatement.commonTransitionToState(Unknown Source
)
   at com.microsoft.jdbc.base.BaseStatement.postImplExecute(Unknown Source)
   at com.microsoft.jdbc.base.BasePreparedStatement.postImplExecute(Unknown Source
)
   at com.microsoft.jdbc.base.BaseStatement.commonExecute(Unknown Source)
   at com.microsoft.jdbc.base.BaseStatement.executeUpdateInternal(Unknown Source)
   at com.microsoft.jdbc.base.BasePreparedStatement.executeUpdate(Unknown Source)
   at net.sf.hibernate.impl.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:
22)
   at net.sf.hibernate.persister.EntityPersister.delete(EntityPersister.java:582)
   at net.sf.hibernate.impl.ScheduledDeletion.execute(ScheduledDeletion.java:29)
   at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2308)
   at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2266)
   at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2187)
   at net.sf.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:61)
   at com.northstar.test.example.BidirectionalConstraintTest.delete(BidirectionalC
onstraintTest.java:52)
   at com.northstar.test.example.BidirectionalConstraintTest.testDeleteConstrained
(BidirectionalConstraintTest.java:28)
   at com.northstar.test.example.BidirectionalConstraintTest.main(BidirectionalCon
straintTest.java:16)
- Could not synchronize database state with session
net.sf.hibernate.JDBCException: could not delete: [com.northstar.test.example.Ri
ght#2]
   at net.sf.hibernate.persister.EntityPersister.delete(EntityPersister.java:601)
   at net.sf.hibernate.impl.ScheduledDeletion.execute(ScheduledDeletion.java:29)
   at net.sf.hibernate.impl.SessionImpl.executeAll(SessionImpl.java:2308)
   at net.sf.hibernate.impl.SessionImpl.execute(SessionImpl.java:2266)
   at net.sf.hibernate.impl.SessionImpl.flush(SessionImpl.java:2187)
   at net.sf.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:61)
   at com.northstar.test.example.BidirectionalConstraintTest.delete(BidirectionalC
onstraintTest.java:52)
   at com.northstar.test.example.BidirectionalConstraintTest.testDeleteConstrained
(BidirectionalConstraintTest.java:28)
   at com.northstar.test.example.BidirectionalConstraintTest.main(BidirectionalCon
straintTest.java:16)
Caused by: java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC][SQ
LServer]DELETE statement conflicted with COLUMN REFERENCE constraint 'LeftToRigh
t'. The conflict occurred in database 'wms_weblogic', table 'test_Left', column
'rightFk'.
   at com.microsoft.jdbc.base.BaseExceptions.createException(Unknown Source)
   at com.microsoft.jdbc.base.BaseExceptions.getException(Unknown Source)
   at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processErrorToken(Unknown Source
)
   at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processReplyToken(Unknown Source
)
   at com.microsoft.jdbc.sqlserver.tds.TDSRPCRequest.processReplyToken(Unknown Sou
rce)
   at com.microsoft.jdbc.sqlserver.tds.TDSRequest.processReply(Unknown Source)
   at com.microsoft.jdbc.sqlserver.SQLServerImplStatement.getNextResultType(Unknow
n Source)
   at com.microsoft.jdbc.base.BaseStatement.commonTransitionToState(Unknown Source
)
   at com.microsoft.jdbc.base.BaseStatement.postImplExecute(Unknown Source)
   at com.microsoft.jdbc.base.BasePreparedStatement.postImplExecute(Unknown Source
)
   at com.microsoft.jdbc.base.BaseStatement.commonExecute(Unknown Source)
   at com.microsoft.jdbc.base.BaseStatement.executeUpdateInternal(Unknown Source)
   at com.microsoft.jdbc.base.BasePreparedStatement.executeUpdate(Unknown Source)
   at net.sf.hibernate.impl.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:
22)
   at net.sf.hibernate.persister.EntityPersister.delete(EntityPersister.java:582)
   ... 8 more


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 01, 2004 11:44 am 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Consider doing thoses actions in the same session or set cascade="save-update" and use detached object feature.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 03, 2004 7:40 pm 
Newbie

Joined: Wed Jan 21, 2004 5:51 pm
Posts: 14
Location: San Francisco, CA
emmanuel wrote:
Consider doing thoses actions in the same session


In my circumstances, it's not possible to use the same session.

Quote:
or set cascade="save-update" and use detached object feature.


Cascade="save-update" forces hibernate to save the FK correctly (problem 2), but I still get the same FK constraint error on deletions (problem 1), which makes sense. Unfortunately, I want to cascade update just the FK field, not the other fields in an object.

I am inferring from your suggestions that there's no way to make Hibernate automatically nullify/update FK constraints when deleting/updating the FK target. Maybe I'll build this functionality myself.

Aren't I already using the detached object feature? I close the session, then do the update in a new session. Is something more required?


Top
 Profile  
 
 Post subject:
PostPosted: Wed Mar 03, 2004 9:42 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
magellan94 wrote:
but I still get the same FK constraint error on deletions (problem 1),

Reread the doc on one-to-one usage, both side must share the same property. constrained="true" shound be used with the foreign generator.


magellan94 wrote:
Aren't I already using the detached object feature? I close the session, then do the update in a new session. Is something more required?

No, saveOrUpdate() is more appropriate for an object graph but that's all

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 04, 2004 12:58 am 
Newbie

Joined: Wed Jan 21, 2004 5:51 pm
Posts: 14
Location: San Francisco, CA
Hi, Emmanuel. Thanks for the rapid reply.

emmanuel wrote:
magellan94 wrote:
but I still get the same FK constraint error on deletions (problem 1),

Reread the doc on one-to-one usage, both side must share the same property. constrained="true" shound be used with the foreign generator.


I tried toggling the 'constrained' setting. I get the constraint error both ways. I'm not sure what you mean by 'both side must share the same property'. The example I posted is almost identical to the bidirectional unique fk example in the reference doc in section 5.1.11. Is something wrong with the way I've mapped the classes?

Stepping through the code I see that the 'constrained' attribute controls whether the property cascades before or after the parent object, depending on the direction of the foreign key. However, in my case, cascade is 'none', so the cascade never occurs regardless of the 'constrained' setting. Even when cascade is 'save-update', as you suggested, the cascade does not occur for deletes. It appears that cascade-delete will occur only if cascade is 'all' or something similar. Unfortunately, this will delete the entire object, not just the FK column, which is not what I want.

I think what I'd like is an approach similar to the way the OneToMany collection persister behaves. It nullifies one-to-many fk columns in the way I want for one-to-one relations.

Here's the approach I'm currently pursuing. Please advise me on its soundness. I'm creating a subclass of EntityPersister that overrides the 'delete' method to execute statements that nullify any fk references to the object. This delete override then calls EntityPersister.delete to nuke the object.


Top
 Profile  
 
 Post subject: Persister that deletes constraints
PostPosted: Thu Mar 04, 2004 3:06 am 
Newbie

Joined: Wed Jan 21, 2004 5:51 pm
Posts: 14
Location: San Francisco, CA
Here's the persister I whipped up to manage the deletion of constraints. It'd be a huge help for me if somebody on the Hibernate team takes a look and lets me know if it looks kosher. Thanks.

Code:
/**
* Like a regular EntityPersister except that it will nullify foreign keys
* columns pointing to the table for this type before executing a deletion.
*
* Created on Mar 3, 2004
* @author DSmith
*/
public class ConstraintDeletingPersister extends EntityPersister {
    private static final Log log = LogFactory.getLog(EntityPersister.class);   

    // arg. I must duplicate this field from EntityPersister because
   // it's not visible and not exposed by any accessor.
   private final SessionFactoryImplementor factory;
   private String[] sqlClearConstraints = null;
   
   /**
     * Get the array of sql statements needed to clear foreign key constraints
     * that reference this object.
    * @return Returns the sqlClearConstraints.
    */
   public String[] getSqlClearConstraints() {
      return sqlClearConstraints;
   }
   
   /**
    * @param model Describes the relational map for this type.
    * @param factory The factory that produces sessions that use this
    * persister.
    * @throws net.sf.hibernate.HibernateException if the configurtion mapping
    * is invalid.
    */
   public ConstraintDeletingPersister(PersistentClass model,
         SessionFactoryImplementor factory) throws HibernateException
   {
      super(model, factory);
      // arg. I must duplicate this field from EntityPersister because
      // it's not visible and not exposed by any accessor.
      this.factory = factory;
   }
   
   /* (non-Javadoc)
    * @see net.sf.hibernate.persister.EntityPersister#postInstantiate()
    */
   public void postInstantiate() throws MappingException {
      super.postInstantiate();
      
      List sqlClearFkList = new ArrayList();
      
      // For each one-to-one property, get the unique fk that references
      // this table and generate a sql string to nullify the fk.
      String[] propertyNames = getPropertyNames();
      Type[] propertyTypes = getPropertyTypes();
      for ( int i=0; i<propertyNames.length; i++ ) {
         Type propType = propertyTypes[i];
         // xxxdsmith are one-to-one types always unique constrained?
         if ( propType.isEntityType() &&
               ((EntityType) propType).isOneToOne()
//                  && ((OneToOneType) uniqueKeyType).getForeignKeyDirection()
//                     == ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT)
               )
         {
            OneToOneType oneToOne = (OneToOneType) propType;
            String[] fkColumns = oneToOne.getReferencedColumns(factory);
            
            if (fkColumns.length > 0) {
               Class clazz = ( (EntityType) propType ).getAssociatedClass();
               // xxxdsmith what if the referenced object uses table-per-subclass?
               // is there a better way to get the table name for the prop?
               EntityPersister fkPersister =
                  (EntityPersister) factory.getPersister(clazz);
               String tableName = fkPersister.getTableName();
               
               // xxxdsmith not sure how to handle where clauses here because
               // I don't know what to use as the alias argument to
               // getSQLWhereString(String alias)
               sqlClearFkList.add(
                  generateClearConstraintString(fkPersister.getTableName(),
                                         fkColumns,
                                         null));
               
            }
         }
      }
      
      this.sqlClearConstraints =
         (String[]) sqlClearFkList.toArray(new String[sqlClearFkList.size()]);
   }
   
   /**
    * Update objects pointing to this entity to remove their foreign keys,
    * then do a normal delete.
    * @see net.sf.hibernate.persister.EntityPersister#delete(java.io.Serializable, java.lang.Object, java.lang.Object, net.sf.hibernate.engine.SessionImplementor)
    */
   public void delete(Serializable id, Object version, Object object,
         SessionImplementor session)
   throws HibernateException {
        clearConstraints(id, version, object, session);
      super.delete(id, version, object, session);
   }
   
   /**
    * @param id
    * @param version
    * @param object
    * @param session
    */
   private void clearConstraints(Serializable id,
                                  Object version,
                                  Object object,
                                  SessionImplementor session)
    throws HibernateException {
       
        if ( log.isTraceEnabled() ) {
            log.trace( "Clearing constraints for entity: " + MessageHelper.infoString(this, id) );
            if ( isVersioned() ) log.trace( "Version: " + version );
        }
       
        try {
            String[] sqlClearStrings = getSqlClearConstraints();
            for (int i = 0; i < sqlClearStrings.length; i++) {
       
                //Render the SQL query
                final PreparedStatement statement;// = session.getPreparedStatement( sqlDelete() );
                statement = session.getBatcher().prepareBatchStatement( sqlClearStrings[i] );
               
                try {
                   
                    // Do the key. The key is immutable so we can use the _current_ object state - not necessarily
                    // the state at the time the delete was issued
                   
                    getIdentifierType().nullSafeSet( statement, id, 1, session );
                   
                    session.getBatcher().addToBatch(-1);
                   
                }
                catch (SQLException sqle) {
                    session.getBatcher().abortBatch(sqle);
                    throw sqle;
                }
            }
        }
        catch (SQLException sqle) {
            throw new JDBCException( "could not clear constraint for: " +  MessageHelper.infoString(this, id), sqle );
        }      
   }

   /**
    * Generate the SQL UPDATE that updates all the foreign keys to null
    */
   protected String generateClearConstraintString(String tableName,
                                       String[] fkColumnNames,
                                       String sqlWhereString)
   {
      Update update = new Update()
         .setTableName(tableName)
         .addColumns(fkColumnNames, "null")
         .setPrimaryKeyColumnNames(fkColumnNames);
      if (sqlWhereString != null) update.setWhere(sqlWhereString);
      return update.toStatementString();
   }
}


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 05, 2004 5:01 am 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
magellan94 wrote:
I'm not sure what you mean by 'both side must share the same property'.

I meant the same id. This should work I guess.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 08, 2004 4:05 pm 
Newbie

Joined: Wed Jan 21, 2004 5:51 pm
Posts: 14
Location: San Francisco, CA
emmanuel wrote:
magellan94 wrote:
I'm not sure what you mean by 'both side must share the same property'.

I meant the same id. This should work I guess.

You seem hesitant to endorse my strategy. What's the cause for your reluctance? Thanks.


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 08, 2004 7:02 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
Well I don't know, I kind of think you don't need to write a custom entity persister for that But I've not played that much with property-ref, it's a clue of a bad DB design.

_________________
Emmanuel


Top
 Profile  
 
 Post subject:
PostPosted: Mon Mar 08, 2004 10:50 pm 
Newbie

Joined: Wed Jan 21, 2004 5:51 pm
Posts: 14
Location: San Francisco, CA
emmanuel wrote:
Well I don't know, I kind of think you don't need to write a custom entity persister for that But I've not played that much with property-ref, it's a clue of a bad DB design.


What's the best way to model a bidirectional 1-to-1 relationship in the DB? I guess you could argue that non-cascading bidirectional 1-to-1 relations are not natural to a database, where the relation is inherently directed from the table with the fk to the constrained table.


Top
 Profile  
 
 Post subject:
PostPosted: Tue Mar 09, 2004 2:43 pm 
Hibernate Team
Hibernate Team

Joined: Sun Sep 14, 2003 3:54 am
Posts: 7256
Location: Paris, France
The best way for a 1-1 is to share the same id in both side of the association. Plus you can use the foreign generator that will set the id properly for you.

_________________
Emmanuel


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

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.