-->
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.  [ 2 posts ] 
Author Message
 Post subject: How to update existing row in DB for transient objects?
PostPosted: Thu Oct 30, 2008 9:23 am 
Newbie

Joined: Wed Oct 29, 2008 12:52 pm
Posts: 2
Hi,

I'm using Hibernate in the database layer of a non-web application. During execution the application creates new/transient objects that are being written to the database at the end of a execution cycle.

The first cycle works as expected and all transient objects are inserted into the database. Starting a second cycle creates a new set of transient objects that to some part overlap with ones from the first cycle. Since the objects are transient, Hibernate inserts new rows into the database and does not care about existing ones with the same natural ID (or insert fails if I define natural ID columns unique).

Code:
public class TestAll {
   private SessionFactory factory = HibernateUtil.getSessionFactory();
   
   @Test
   public final void testDbSaveTwice() {
      try {
         ParentObject p1  = createParentObject();
         ChildObject   c1 = createChildObject();
         p1.addChildObject(c1);
         write(p1);

         ParentObject p2  = createParentObject();
         ChildObject   c2 = createChildObject();
         p2.addChildObject(c2);
         p2.setParentData(p2.getParentData() + " - modified");
         c2.setChildData(c2.getChildData() + " - modified");
         write(p2);
      }
      catch(Exception e) {
         fail(e.getMessage());
      }
   }
   
   private ParentObject createParentObject() {
      ParentObject rval = new ParentObject("my unique parent object name");
      rval.setParentData("some parent data");      
      return rval;
   }
   
   private ChildObject createChildObject() {
      ChildObject rval = new ChildObject("my unique child object name");
      rval.setChildData("some child data");
      return rval;
   }
   
   public void write(Object obj) {
      Session session = factory.openSession();
      Transaction transaction = null;
      
      try {
         transaction = session.beginTransaction();
         session.saveOrUpdate(obj);
         transaction.commit();
      }
      catch(RuntimeException e) {
         if(transaction != null) transaction.rollback();
         e.printStackTrace();
         throw(e);
      }
      finally {
         session.close();
      }
   }
}


Since I'm new to Hiberante I followed the advice to have Hibernate assign surrogate primary keys and define natural IDs with equals() and hashcode() implemented accordingly:

Code:
@Entity
public class ParentObject {

   @Id @GeneratedValue
   private Long   id;

   @Column(length = 100, nullable = false)
   @org.hibernate.annotations.NaturalId
   private String uniqName = new String();
   
   @Basic
   private String parentData;
   
   @OneToMany(mappedBy="parentObject", cascade=CascadeType.ALL)
   private List<ChildObject> childObjects  = new ArrayList<ChildObject>();

        ...

   public void addChildObject(ChildObject childObject) {
      childObjects.add(childObject);
      childObject.setParentObject(this);
   }
   
   @Override
   public boolean equals(Object obj) {
      if(this == obj) return true;
      if(obj == null) return false;
      if(getClass() != obj.getClass()) return false;

      ParentObject other = (ParentObject) obj;      
      return uniqName.equals(other.uniqName);
   }
   
   @Override
   public int hashCode() {
      return uniqName.hashCode();
   }
}


Code:
@Entity
public class ChildObject {

   @Id @GeneratedValue
   private Long   id;

   @Column(length = 100, nullable = false)
   @org.hibernate.annotations.NaturalId
   private String    uniqName;   

   @Basic
   private String    childData;
   
   @ManyToOne
   @JoinColumn(nullable=false)
   private ParentObject    parentObject;

        ...
   
   @Override
   public boolean equals(Object obj) {
      if(this == obj) return true;
      if(obj == null) return false;
      if(getClass() != obj.getClass()) return false;
      
      ChildObject other = (ChildObject) obj;
      return uniqName.equals(other.uniqName);
   }
   
   @Override
   public int hashCode() {
      return uniqName.hashCode();
   }
}



I was able to force (or trick) Hibernate into updating exising rows by looking up surrogate ID if existing, and set that primary ID of the transient object (Hibernate then treats that object as detached). While such change is fine for single objects, it causes some additional effort in duplicating what cascading updates already provide for associated objects.

When I learned about event listeners (in my case SaveOrUpdateEventListener) I added that primary key lookup in a custom event listener before calling the onSaveOrUpdate() of the DefaultSaveOrUpdateEventListner. That proved to work for entities without associated objects, but resulted in an endless recursion with associated objects. Hibernate triggers a flush before executing my ID lookup query in the event listener for the associated object which again triggers SaveOrUpdate...

Code:
public class TestSafeOrUpdateListener extends DefaultSaveOrUpdateEventListener {
   private final Logger         logger = LoggerFactory.getLogger(TestSafeOrUpdateListener.class);

   @Override
   public void onSaveOrUpdate(SaveOrUpdateEvent event) {
      if(event.getObject() instanceof ParentObject) {
         logger.debug("saveOrUpdate fired type ParentObject");
         ParentObject parentObject = (ParentObject)event.getObject();
         Long id = lookupParentObjectId(event);
         if(id != null) {
            parentObject.setId(id);
         }
      }
      else if(event.getObject() instanceof ChildObject) {
         logger.debug("saveOrUpdate fired for type ChildObject!");
         ChildObject childObject = (ChildObject)event.getObject();
         Long id = lookupChildObjectId(event);
         if(id != null) {
            childObject.setId(id);
         }
      }
      else {
         logger.debug("saveOrUpdate fired for unhandled type!");
      }
      
      super.onSaveOrUpdate(event);
   }
   
   private Long lookupParentObjectId(SaveOrUpdateEvent event) {
      Session session  = event.getSession();
      ParentObject parentObject = (ParentObject)event.getObject();
      Query getIdQuery = session.createQuery("select parentObject.id from ParentObject parentObject where parentObject.uniqName = :uniqName");
      
      getIdQuery.setParameter("uniqName", parentObject.getUniqName());
      getIdQuery.setCacheable(true);
      Long id = (Long)getIdQuery.uniqueResult();

      return id;
   }
   
   private Long lookupChildObjectId(SaveOrUpdateEvent event) {
      Session session  = event.getSession();
      ChildObject childObject    = (ChildObject)event.getObject();
      Query getIdQuery = session.createQuery("select childObject.id from ChildObject childObject where childObject.uniqName = :uniqName");
      
      getIdQuery.setParameter("uniqName", childObject.getUniqName());
      getIdQuery.setCacheable(true);
      Long id = (Long)getIdQuery.uniqueResult();
   
      return id;
   }
}


Is there a way to have Hibernate check if entities exist in the database for a mesh of transient objects based on natural IDs or some other identifying properties?

I'm using Hibernate 3.3.1.GA, Hibernate annotations 3.4.0.GA and Apache Derby 10.4.2.0.

Best regards,
Manfred


Top
 Profile  
 
 Post subject: Re: How to update existing row in DB for transient objects?
PostPosted: Thu Oct 30, 2008 12:49 pm 
Newbie

Joined: Wed Oct 29, 2008 12:52 pm
Posts: 2
Got a little further: using JDBC to check for existing IDs in the event listener does not trigger a flush and therefore allows to update my transient objects to "become" detached objects:

Code:
   private Long lookupParentObjectIdSql(SaveOrUpdateEvent event) {
      ParentObject parentObject    = (ParentObject)event.getObject();
      Long id = null;
      try {
         Connection sqlConnection = event.getSession().connection();         
         PreparedStatement stmt = sqlConnection.prepareStatement("select id from app.parentobject where uniqname = ?");
         stmt.setString(1, parentObject.getUniqName());
         ResultSet rs = stmt.executeQuery();
         if(rs.next()) {
            id = rs.getLong(1);
         }
         rs.close();
      }
      catch(SQLException e) {
         e.printStackTrace();
      }
      
      return id;
   }

   private Long lookupChildObjectIdSql(SaveOrUpdateEvent event) {
      ChildObject childObject    = (ChildObject)event.getObject();
      Long id = null;
      try {
         Connection sqlConnection = event.getSession().connection();         
         PreparedStatement stmt = sqlConnection.prepareStatement("select id from app.childobject where uniqname = ?");
         stmt.setString(1, childObject.getUniqName());
         ResultSet rs = stmt.executeQuery();
         if(rs.next()) {
            id = rs.getLong(1);
         }
         rs.close();
      }
      catch(SQLException e) {
         e.printStackTrace();
      }
      
      return id;
   }



While this seems to work (although I don't have a clue if such modifications may cause later problems in Hibernate) I'd like to know how to do it right?

What's the correct way to have Hibernate automatically validate if an entity has been already stored in the database that's equal (in terms of business keys/natural IDs) for a new transient object?

Best regards,
Manfred


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 2 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.