-->
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.  [ 14 posts ] 
Author Message
 Post subject: Auto-generated key and errors on insert
PostPosted: Thu May 04, 2006 2:21 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
Hi,

I configured an hibernate mapping to use an auto-generated key from database, using de following code:

Code:
       <id name="seq" column="seq" type="long" unsaved-value="0">
               <generator class="sequence">
               <param name="sequence">MY_SEQ</param>
               </generator>
       </id>


I decided in my application that objects with seq=0 need to be inserted. But part of a complex business logic is in the database, and is validated during the insert statement. So, an insert could return an error, and in this case is not executed. The object is returned to the user, that can modify some properties and try a new insert.
The problem is that when an error occurs in the database on insert, the auto-generated key is set in the object, and the seq is not 0 anymore. The user then tries to insert, but an "Unexpected row count" is thrown.

One simple solution should be set the property to 0 in the application, but in my case, the object is part of a complex master/detail/detail structure, and is very difficult to do this.

The question is: Is there any way to set the auto-generated property only if no errors occurs during the execution of the statement? That is not supposed to be the standard behavior?

Sorry for my "not so good" english, and Thanks in advance.

Hibernate version: 3.0.5

Name and version of the database you are using: Oracle 10g

The generated SQL (show_sql=true):
Before first insert try:
Code:
insert into POJ_HORARIODIAESCALA (DT_HORA_ENTRADA, DT_HORA_SAIDA, TOLER_MINU_ANTES_ENTRADA, TOLER_MINU_ANTES_SAIDA, TOLER_MINU_APOS_ENTRADA, TOLER_MINU_APOS_SAIDA, DIES_SEQ, seq) values (?, ?, ?, ?, ?, ?, ?, ?)

This statement returns an error from database, and

After first insert try:
Code:
update POJ_HORARIODIAESCALA set DT_HORA_ENTRADA=?, DT_HORA_SAIDA=?, TOLER_MINU_ANTES_ENTRADA=?, TOLER_MINU_ANTES_SAIDA=?, TOLER_MINU_APOS_ENTRADA=?, TOLER_MINU_APOS_SAIDA=? where seq=?



Exceptions after first insert try:
Code:
SEVERE: Could not synchronize database state with session
org.hibernate.HibernateException: Unexpected row count: 0 expected: 1
   at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:32)
   at org.hibernate.persister.entity.BasicEntityPersister.update(BasicEntityPersister.java:1982)
...


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 04, 2006 2:24 pm 
Newbie

Joined: Thu Feb 17, 2005 10:49 am
Posts: 6
Are you wrapping the save in a transaction? You could roll it back on the exception.

_________________
Thanks,
Greg


Top
 Profile  
 
 Post subject:
PostPosted: Thu May 04, 2006 2:45 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
Apparently, the trans.rollback() does not set back the value of the property:

Code:
      Session session = sessionFactory.openSession(this.getConnection());
      Transaction trans = session.beginTransaction();
      
      try {
         session.saveOrUpdate(escala);
         trans.commit();
         
      } catch (JDBCException e) {

         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", e);
            throw new RuntimeException("Erro ao desfazer transação.", e);
         }
         
         LOG.log(e.getSQLException(), e);
         throw new RuntimeException(e.getSQLException(), e);
         
      } catch (Exception e) {
      
         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", e);
            throw new RuntimeException("Erro ao desfazer transação.", e);
         }
      
         LOG.error("Erro inesperado.", e);
         throw new RuntimeException("Erro inesperado.", e);
         
      } finally {
         if (session != null) {
            try {
               session.close();
            } catch (Exception ignored){}
         }
      }


Top
 Profile  
 
 Post subject:
PostPosted: Fri May 05, 2006 12:26 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
I'm still trying to resolve the problem, I removed the option unsaved-value="0" and leave to hibernate to handle which objects need to be saved.
But it seems that unsaved-value="0" is the default option, and this alteration changed nothing in the application behavior...
Any thoughts?


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 08, 2006 2:38 am 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
Since my first question was not answered yet, I will try to simplify: I need to know if there is any way to leave the database generated properties of an object untouched if any error occurs during the execution of a sql statement (insert, update or delete).


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 09, 2006 1:40 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
After almost a week struggled with this problem, I finally found a way to circunvent it. Reading and rereading the docs, I finally understanded exactly what the Session.merge(...) method is for, and it can fulfill my needs. I replaced the lines
Code:
session.update(escala);
trans.commit();

with the lines
Code:
escala_ = (Escala)session.merge(escala);
session.persist(escala_);
trans.commit();

So, despite the more work that the application must do with the merge() call, my requirements was satisfied: if noting go wrong during the database execution, the escala_ object has the state that I want, and if any error occurs, the escala object has the relevant state.

With the experience and research during these days, I would like to do some considerations:
The fact that the state of the objects can change during the transaction (when the framework sets the generated keys) and is not restored when it is rolled back could induce errors. The semanthics of the rollback operation is that the state of the objects should return to the same state that was before the start of the transaction. I know that it is a bit difficult to implement, but I expect that one day this could be did.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 09, 2006 3:13 pm 
Expert
Expert

Joined: Tue Apr 25, 2006 12:04 pm
Posts: 260
I dont understand why session.update( escala ) didnt worked for you. Have you checked how many sqls are logged instead of one update statement with the second approach.

session.update( escala ) should work, if you have correct "seq" attribute value inside that object.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 09, 2006 3:53 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
Ok, I will try to explain with the model:
The escala object is like a master/detail/detail object, and I have:
[Escala] 1:n [DiaEscala] 1:n [HorarioDiaEscala].
Before the session.update(escala), the output for the first HorarioDiaEscala object is: escala.diaEscala[0].horarioDiaEscala[0].seq=0
Then I have the update operation, and some error occurs in the database (this error is expected, because the new state falls in some business logic implemented at database level).
After that error, and after the rollback operation, the output was:
escala.diaEscala[0].horarioDiaEscala[0].seq=189
That is the new generated key. But I don't want that new generated key, because a new call to the update operation will not try to insert the new HorarioDiaEscala object, but will try to update an object that still not exists in the database...
Was it more clear?
If some information is missing, I can reproduce a less complex example to be discussed here.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 09, 2006 4:42 pm 
Expert
Expert

Joined: Tue Apr 25, 2006 12:04 pm
Posts: 260
Quote:
Then I have the update operation, and some error occurs in the database (this error is expected, because the new state falls in some business logic implemented at database level).


I dont know if that is advisable to have some business logic at the database level.

Quote:
That is the new generated key

Do you mean the primary key is changed?

Your mapping files and the code you are using to do your respective updates will be helpful to make some comments that are more meaningful.


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 09, 2006 5:58 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
So, let's go with a less complex example. Two classes, Person and Address:

Code:
package test.domain;

import java.util.Collection;

public class Person {
   private long seq;
   private String name;
   private Collection addresses;
   
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public long getSeq() {
      return seq;
   }
   public void setSeq(long seq) {
      this.seq = seq;
   }
   public Collection getAddresses() {
      return addresses;
   }
   public void setAddresses(Collection addresses) {
      this.addresses = addresses;
   }
}


Code:
package test.domain;

public class Address {
   private long seq;
   private String description;
   public String getDescription() {
      return description;
   }
   public void setDescription(String description) {
      this.description = description;
   }
   public long getSeq() {
      return seq;
   }
   public void setSeq(long seq) {
      this.seq = seq;
   }
}


The mapping files:

Person.hbm.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

        <class name="test.domain.Person" table="PERSON">
                <id name="seq" column="seq" type="long">
                   <generator class="sequence">
                      <param name="sequence">TSTQ_PER</param>
                   </generator>
                </id>
                <property name="name" column="NAME"/>
             <set name="addresses" cascade="all-delete-orphan">
               <key column="PER_SEQ" not-null="true"/>
               <one-to-many class="test.domain.Address"/>
             </set>
        </class>

</hibernate-mapping>


Address.hbm.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

        <class name="test.domain.Address" table="ADDRESS">
                <id name="seq" column="seq" type="long">
                   <generator class="sequence">
                      <param name="sequence">TSTQ_ADD</param>
                   </generator>
                </id>
                <property name="description" column="DESCRIPTION"/>
        </class>

</hibernate-mapping>


Two tables:

Code:
create table PERSON
(
  SEQ  NUMBER not null,
  NAME VARCHAR2(200) not null
)

create table ADDRESS
(
  SEQ         NUMBER not null,
  DESCRIPTION VARCHAR2(200) not null,
  PER_SEQ     NUMBER not null
)


The PersonDAO.java:

Code:
package test.dao;

import java.sql.Connection;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.JDBCException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

import test.domain.Person;

import br.gov.rs.mp.util.HibernateUtil;
import br.gov.rs.mp.util.sql.SQLExceptionWrapper;

public class PersonDAO {
   
   private final static Logger LOG = Logger.getLogger(PersonDAO.class.getName());
   private static final SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
   private Connection connection;
   
   public PersonDAO(Connection connection){
      this.connection = connection;
   }
   
   public Connection getConnection() {
      return connection;
   }
   
   public List getList() {
      List persons = null;
      
      try {
         Session session = sessionFactory.openSession(getConnection());
         Query q = session.createQuery("from Person");
         persons = q.list();
         session.close();
      } catch (Exception e) {
         LOG.error("Erro inesperado.", e);
         throw new RuntimeException("Erro inesperado.", e);
      }
      
      return persons;
   }
   
   public Person get(Person per){
      
      Person person = null;
      
      try {
         Session session = sessionFactory.openSession(getConnection());
         
         person = (Person)session.createCriteria(Person.class)
            .setFetchMode("addresses", FetchMode.JOIN)
            .add( Restrictions.idEq(new Long(per.getSeq())) )
                .uniqueResult();
         Hibernate.initialize(person.getAddresses());
         session.close();
      } catch (Exception e) {
         LOG.error("Erro inesperado.", e);
         throw new RuntimeException("Erro inesperado.", e);
      }
      
      return person;

   }
   
   public boolean delete(Person person) {
      
      Session session = sessionFactory.openSession(getConnection());
      Transaction trans = session.beginTransaction();
      
      try {
         person = this.get(person);
         session.delete(person);
         trans.commit();
         
      } catch (JDBCException e) {

         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", ex);
         }
         
         SQLExceptionWrapper wrapper = new SQLExceptionWrapper(e.getSQLException(), getConnection());
         LOG.log(wrapper.getLoggerPriority(), wrapper.getMessage(), e);
         throw new RuntimeException(wrapper.getMessage(), e);
         
      } catch (Exception e) {
         
         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", ex);
         }
         
         LOG.error("Erro inesperado.", e);
         throw new RuntimeException("Erro inesperado.");
         
      } finally {
         if (session != null) {
            try {
               session.close();
            } catch (Exception ignored){}
         }
      }
      
      return true;
   }
   
   public boolean insert(Person person) {
      
      Session session = sessionFactory.openSession(getConnection());
      Transaction trans = session.beginTransaction();
      
      try {
         session.save(person);
         trans.commit();
         
      } catch (JDBCException e) {

         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", ex);
         }
         
         SQLExceptionWrapper wrapper = new SQLExceptionWrapper(e.getSQLException(), getConnection());
         LOG.log(wrapper.getLoggerPriority(), wrapper.getMessage(), e);
         throw new RuntimeException(wrapper.getMessage(), e);
         
      } catch (Exception e) {
         
         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", ex);
         }
         
         LOG.error("Erro inesperado.", e);
         throw new RuntimeException("Erro inesperado.");
         
      } finally {
         if (session != null) {
            try {
               session.close();
            } catch (Exception ignored){}
         }
      }
      return true;
   }
   
   public boolean update(Person person) {
      Session session = sessionFactory.openSession(getConnection());
      Transaction trans = session.beginTransaction();
      
      try {
         session.update(person);
         trans.commit();
         
      } catch (JDBCException e) {

         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", ex);
         }
         
         SQLExceptionWrapper wrapper = new SQLExceptionWrapper(e.getSQLException(), getConnection());
         LOG.log(wrapper.getLoggerPriority(), wrapper.getMessage(), e);
         throw new RuntimeException(wrapper.getMessage(), e);
         
      } catch (Exception e) {
         
         try {
            trans.rollback();
         } catch (Exception ex) {
            LOG.error("Erro ao desfazer transação.", ex);
         }
         
         LOG.error("Erro inesperado.", e);
         throw new RuntimeException("Erro inesperado.");
         
      } finally {
         if (session != null) {
            try {
               session.close();
            } catch (Exception ignored){}
         }
      }
      
      return true;
   }

}


And now the main method:

Code:
   public static void main(String[] args) {
      try {
         Class.forName("oracle.jdbc.driver.OracleDriver");
         Connection conn = DriverManager.getConnection(...);
         
         PersonDAO dao = new PersonDAO(conn);
         
         Person person = new Person();
         person.setName("Me");
         dao.insert(person);
         
         person = dao.get(person);
         
         Address addr = new Address();
         person.getAddresses().add(addr);
         System.out.println("address.seq=" + addr.getSeq());
         
         try {
            // Here, some error occurs, at database level:
            // address.description can't be null
            dao.update(person);
         } catch (RuntimeException e) {
            e.printStackTrace();
         }
         
         System.out.println("address.seq=" + addr.getSeq());

         // now sets the description
         addr.setDescription("New Address");
         
         try {
            // Now, no error occurs at database level, but errors occurs
            // because hibernate tries to update an address that
            // still does not exists in the database
            dao.update(person);
         } catch (RuntimeException e) {
            e.printStackTrace();
         }
         
      } catch (Exception e) {
         e.printStackTrace();
         System.exit(0);
      }

   }


The stacktrace shows an error caused by the first update (ORA-01400: cannot insert NULL into ("ADDRESS"."DESCRIPTION"),
followed by another error caused by the second update (Could not synchronize database state with session,
org.hibernate.StaleStateException: Unexpected row count: 0 expected: 1)
The two outs show different values for address.seq.

Please understand, my application MUST handle database level errors, this is a big requirement for compatibility with other existent systems.

Thanks in advance


Top
 Profile  
 
 Post subject:
PostPosted: Tue May 09, 2006 8:51 pm 
Expert
Expert

Joined: Tue Apr 25, 2006 12:04 pm
Posts: 260
Given these tables

Code:
create table PERSON
(
  SEQ  NUMBER not null,
  NAME VARCHAR2(200) not null
)

create table ADDRESS
(
  SEQ         NUMBER not null,
  DESCRIPTION VARCHAR2(200) not null,
  PER_SEQ     NUMBER not null          -- i guess this is foreign key to PERSON table
)


my mapping files differ from yours.

Person.hbm.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
<class name="test.domain.Person" table="PERSON">
   <id name="seq" column="seq" type="long">
      <generator class="sequence">
         <param name="sequence">TSTQ_PER</param>
      </generator>
   </id>
   <property name="name" column="NAME"/>
   <set name="addresses" cascade="all-delete-orphan">
      <key column="SEQ" not-null="true"/>
      <one-to-many class="test.domain.Address"/>
   </set>
</class>
</hibernate-mapping>


Address.hbm.xml
Code:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="test.domain.Address" table="ADDRESS">
   <id name="seq" column="seq" type="long">
      <generator class="sequence">
         <param name="sequence">TSTQ_ADD</param>
      </generator>
   </id>
   <property name="description" column="DESCRIPTION"/>
   <property name="personSeq"   column="PER_SEQ"/>
   
   <many-to-one name="personObj" class="test.domain.Person" insert="false" update="false" column="PER_SEQ"/>
</class>

</hibernate-mapping>


The Java files for these mapping files should be trivial for you.

Observe the differnce you had in the set element in Person.hbm.xml; you mentioned PER_SEQ column which doesnt exist in Person table whereas it exists in Address table. I hope you can understand it now.


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 10, 2006 11:40 am 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
bkmr_77,
Your mapping files suppose that I must have a property named personSeq and one named personObj in the class Address, and this characterizes a bidirectional association between Person and Address. I need mapping files that adapt exactly to the model that I provided, with an unidirectional association between Person and Address, because, in the real case, some parts of the application are already implemented and the change of the model classes are not an option. Or it's presumed that I must change my model to adapt to hibernate?


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 10, 2006 11:51 am 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
Sorry for my bad english...
"... the change of the model classes *is* not an option. ..."


Top
 Profile  
 
 Post subject:
PostPosted: Wed May 10, 2006 5:36 pm 
Newbie

Joined: Thu May 04, 2006 1:11 pm
Posts: 12
Only for completion of the above example, the entire DDL should be:

Code:
create table PERSON
(
  SEQ  NUMBER not null, -- primary key
  NAME VARCHAR2(200) not null
);

alter table PERSON
  add constraint PER_PK primary key (SEQ);

create table ADDRESS
(
  SEQ         NUMBER not null, -- primary key
  DESCRIPTION VARCHAR2(200) not null,
  PER_SEQ     NUMBER not null -- fk to PERSON(SEQ)
);

alter table ADDRESS
  add constraint ADD_PK primary key (SEQ);
 
alter table ADDRESS
  add constraint ADD_PER_FK foreign key (PER_SEQ)
  references PERSON (SEQ);


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