List and OneToMany updating

I have this bean in the model:

<bean
  id="Contract-payslips"
  class="org.jspresso.framework.model.descriptor.basic.BasicCollectionPropertyDescriptor">
  <property
    name="name"
    value="payslips" />
  <property
    name="composition"
    value="true" />
  <property
    name="referencedDescriptor">
    <bean
      class="org.jspresso.framework.model.descriptor.basic.BasicCollectionDescriptor">
      <property
        name="collectionInterface"
        value="java.util.Set" />
      <property
        name="elementDescriptor"
        ref="Payslip" />
    </bean>
  </property>
</bean>

Everything runs fine.

After I replace Set by List as collectionInterface, I get this exception:

org.springframework.jdbc.UncategorizedSQLException: Hibernate operation: Could not execute JDBC batch update; uncategorized SQLException for SQL [insert into PAYSLIP (VERSION, GROSS_WAGE, PAYMENT_DATE, REF_PERIOD, ID) values (?, ?, ?, ?, ?)]; SQL state [null]; error code [0]; failed batch; nested exception is java.sql.BatchUpdateException: failed batch
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.translate(SQLStateSQLExceptionTranslator.java:121)
at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.translate(SQLErrorCodeSQLExceptionTranslator.java:322)
at org.springframework.orm.hibernate3.HibernateAccessor.convertJdbcAccessException(HibernateAccessor.java:424)
at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernateAccessException(HibernateAccessor.java:410)
at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:378)
at org.springframework.orm.hibernate3.HibernateTemplate.saveOrUpdate(HibernateTemplate.java:693)
at org.popsuite.hr.development.AbstractTestDataPersister.saveOrUpdate(AbstractTestDataPersister.java:72)
at org.popsuite.hr.development.TestDataPersister.persistTestData(TestDataPersister.java:64)
at org.popsuite.hr.model.service.ContractServiceDelegateIntegrationTest.setUp(ContractServiceDelegateIntegrationTest.java:52)
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.internal.runners.BeforeAndAfterRunner.invokeMethod(BeforeAndAfterRunner.java:74)
at org.junit.internal.runners.BeforeAndAfterRunner.runBefores(BeforeAndAfterRunner.java:50)
at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:33)
at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75)
at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45)
at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66)
at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35)
at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42)
at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34)
at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.sql.BatchUpdateException: failed batch
at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
at org.apache.commons.dbcp.DelegatingStatement.executeBatch(DelegatingStatement.java:294)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:237)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:141)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:390)
at org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:374)
... 24 more

with this code:

 

  public void persistTestData() {

Country france = createCountry("France", "fr");

City paris = createCity("Paris", "75000", france);

Branch design2see = createBranch("Design2See",
"123 avenue de la Liberté", paris, "contactatdesign2see [dot] com",
"+33 123 456 000");

Worker worker = createWorker("Dupond", null, "Pierre", "M",
"12 allée du Chien qui Fume", paris, "01/02/60", "StBrieuc", design2see);

Set rubrics = new HashSet();

rubrics.add(createRubric("URSSAF AT", 100)); // en millième de %
rubrics.add(createRubric("FNGS", 500)); // en millième de %

Profil profil = createProfil("Technicien", rubrics);

Job job = createJob("journaliste", null, profil);

Contract contract = createContract(1000, "01/10/07", (String) null, job, worker);

createPayslip("30/10/07", contract);

saveOrUpdate(design2see);
}

In org.popsuite.hr.model.Contract, the generated code is:

 

  /**
* Gets the payslips.
*
* @hibernate.list
* cascade = "persist,merge,save-update,refresh,evict,replicate,delete"
* @hibernate.key
* column = "WORK_CONTRACT_ID"
* @hibernate.one-to-many
* class = "org.popsuite.hr.model.Payslip"
* @hibernate.list-index
* column = "PAYSLIPS_SEQ"
* @return the payslips.
*/
java.util.List getPayslips();


There is no inverse attribute in hibernate.list. Could it be the origine of the exception?

List and OneToMany updating

Hi,

It definitely has something to do with the Hibernate mapping. One strange thing with the failing query is that there is no PAYSLIPS_SEQ nor WORK_CONTRACT_ID columns involved. From your mapping it should be there to persist the collection with the list sementics. Reading your post, you got rid of the xml hibernate mapping to move to annotations. Are you sure that everything in your application is actually using the annotations and not some xml mapping you would still have in your classpath ?

I would investigate as following :

  • get rid of Hibernate annotations
  • re-generate your entities using the "standard" Jspresso process with collectionInterface set to java.util.List in your model Spring context file.
  • re-test. If everything runs fine, check the differences between Hibernate annotations and xml mapping files.

BTW, there is the same kind of relationship in the HR sample application between employee and event. No inverse attribute either so I don't think that this is related.

At the begining of Jspresso, I had a look to hibernate annotations to get rid of mapping files but the coverage was not complete then and we were missing some important features (especially for persisting proxies). Things must have changed now. We could improve the freemarker template used to generate the entities to choose between annotations and xml mapping; but this is somehow transparent to the developper since there is hardly anything you would need to change manually instead of re-generating the model entities.

Hope this helps,
Vincent

List and OneToMany updating

The OneToMany relation between employee and event is monodirectional.

The OneToMany relation between contract and payslip is bidirectional. So it needs to specifiy

    not-null = "true"

as attribute of @hibernate.key on the OneToMany side.

cf. http://www.hibernate.org/116.html#A13

 

How to change the model.xml file to generate this code?

List and OneToMany updating

Hi,

First of all, forget all about my last post regarding hibernate annotations. I don't know why but I read annotations instead of Xdoclet tags... (it has been a hard weekWink). After having some sleep, I think that the problem you face comes from the fact that you declare the one end (the contract side) of the relationship as mandatory (that translates to Hibernate not-null). In that case, Hibernate generates a not null constraint on the underlying column (WORK_CONTRACT_ID) and this is fair.

But whenever you try to persist the payslips, for a reason I ignore, Hibernate first generates an INSERT statement without the WORK_CONTRACT_ID and PAYSLIPS_SEQ columns involved and just after an UPDATE with those 2 columns... And the INSERT fails with a not null constraint violation from HSQL. Putting not-null="true" on @hibernate.key makes things even worse since the related error is from Hibernate itself an quite hard to diagnose since your entity is actually fine in memory ("not null property references a null or transient value...").

But whenever you get rid of the not-null on both sides, everything runs fine.

As a follow-up, you can configure constraints on most relational databases (except HSQL DB as far as I know and this is bad for the development phase) to apply the checks at the end of the transaction instead of after each statement. This would solve the not-null problem since at the end of your transaction all constraints are actually valid (but invalid after the first INSERT statement).

An other but little less secure workaround is to tweak the Jspresso generated hibernate mapping to get rid of the generated not-null="true" on the one side but keep the mandatory definition in the Jspresso model.xml file. In that case you make the backend more permissive (actually accepting null values) but you will still check the constraint on the application side.

To be complete, here is how you would make the employee-event relationship bidirectional in the HR sample application. First define the reverse end (Event -> Employee) of the relationship :

  <bean
    id="Event.employee"
    class="org.jspresso.framework.model.descriptor.basic.BasicReferencePropertyDescriptor">
    <property
      name="name"
      value="eventEmployee" />
    <property
      name="referencedDescriptor"
      ref="Employee" />
    <property
      name="mandatory"
      value="true" />
    <property
      name="reverseRelationEnd"
      ref="Employee-events" />
  </bean>

and reference this new Event.employee property descriptor in the Employee entity :

  <bean
    id="Event"
    class="org.jspresso.framework.model.descriptor.entity.basic.BasicEntityDescriptor">
    ...
    <property
      name="propertyDescriptors">
      <list>
        ...
        <ref bean="Event.employee" />
      </list>
    </property>
  </bean>

Hope this time it helps. Don't hesitate to post back if you manage to find any better explanation.

Regards,

Vincent

List and OneToMany updating

I just extracted the definition of the manytoone side of the relation and put it in a bean. So now the payslip model is:

 

  <bean
    id="Payslip.contract"
    class="org.jspresso.framework.model.descriptor.basic.BasicReferencePropertyDescriptor">
    <property name="name"                 value="workContract" />
    <property name="referencedDescriptor" ref="Contract" />
    <property name="reverseRelationEnd"   ref="Contract-payslips" />
    <property name="mandatory"            value="true" />
  </bean>

  <bean
    id="Payslip"
    class="org.jspresso.framework.model.descriptor.entity.basic.BasicEntityDescriptor">
    <constructor-arg value="org.popsuite.hr.model.Payslip" />
    <property name="propertyDescriptors">
      <list>
        <bean class="org.jspresso.framework.model.descriptor.basic.BasicIntegerPropertyDescriptor">
          <property name="name"                 value="grossWage" />
        </bean>
        <bean class="org.jspresso.framework.model.descriptor.basic.BasicDatePropertyDescriptor">
          <property name="name"                 value="paymentDate" />
          <property name="type"                 ref="DATE" />
        </bean>
        <bean class="org.jspresso.framework.model.descriptor.basic.BasicDatePropertyDescriptor">
          <property name="name"                 value="refPeriod" />
          <property name="type"                 ref="DATE" />
          <property name="description"          value="last day of the period itself" />
        </bean>
        <ref bean="Payslip.contract" />
        <ref local="Payslip-payitems" />
      </list>
    </property>
    <property name="serviceDelegates">
      <map>
        <entry key="org.popsuite.hr.model.service.PayslipService">
          <bean class="org.popsuite.hr.model.service.PayslipServiceDelegate" >
            <property name="entityFactory" ref="basicEntityFactory"/>
          </bean>
        </entry>
      </map>
    </property>
  </bean>

The result is the same. No surprise here ;-)

As soon as I add a constraint not null on the underlying column (WORK_CONTRACT_ID) on the onetomany side,

  in the java class:

   * @hibernate.key
   *           column = "WORK_CONTRACT_ID"
   *           not-null="true"

  in the hibernate definition:

      <key column="WORK_CONTRACT_ID" not-null="true"/>

Hibernate is happy and does the job.

No need to remove all the constraints or to deal with the database configuration.

The problem of list with bidirectional relation seems an old one for Hibernate, with a solution only since the version 3.2.2: to put a not null constraint on the underlying column (WORK_CONTRACT_ID) on both sides of the relation, as described on

 http://www.hibernate.org/116.html#A13

 

IMO there is no reason to do otherwise. The question becomes how to deal with it from the model:

- either automatically when a bidirection relation is defined with a list interface

- either by hand with a specific attribute, and then which one.

 

 

 

List and OneToMany updating

By the way, the log is now:

 

DEBUG <2008-11-16 10:42:14,234> org.hibernate.jdbc.AbstractBatcher : about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
DEBUG <2008-11-16 10:42:14,234> org.hibernate.SQL : update PAYSLIP set WORK_CONTRACT_ID=?, PAYSLIPS_SEQ=? where ID=?
DEBUG <2008-11-16 10:42:14,234> org.hibernate.persister.collection.AbstractCollectionPersister : done inserting rows: 1 inserted
DEBUG <2008-11-16 10:42:14,234> org.hibernate.jdbc.AbstractBatcher : Executing batch size: 1
DEBUG <2008-11-16 10:42:14,234> org.hibernate.jdbc.AbstractBatcher : about to close PreparedStatement (open PreparedStatements: 1, globally: 1)

List and OneToMany updating

I investigated further to understand why the not-null="true" on the oneToMany side didn't do the trick for me... There is actually a bug in the ApplicationSessionAwareEntityProxyInterceptor class that is responsible for all the instance lifecycle of the Jspresso entities regarding Hibernate.

Once I fixed it, everything ran fine. The problem is that Hibernate handles extra properties to manage collections that are not known by the entity. Such properties are for instance "_[collectionName]BackRef" and "_[collectionName]IndexBackRef". The bug made these "special" properties reset to null when persisting an entity, thus removing the underlying columns from the first INSERT statement and violating the database not-null constraint.

I think that it works for you because you have used the EntityProxyInterceptor that does not have this bug since it doesn't handle the dirty checking in place of Hibernate.

I've just uploaded a snapshot release (2.2.2-SNAPSHOT) of the framework in the Jspresso maven snapshot repository that includes this fix as well as the new template to generate the entities with the not-null="true" on the @hibernate.key of the oneToMany side. I've taken the option of adding it only when the manyToOne side is declared mandatory in the model (this is your case).

This snapshot release fixes also a major bug for entities deletion in Jspresso applications using the standard remove action.

To test the fix you can :

  1. edit the root pom.xml and change to <version>2.2.2-SNAPSHOT</version> in the <parent> section
  2. edit the ant build.xml to change all Jspresso version references to 2.2.2-SNAPSHOT
  3. do a mvn package (this will download everything to your local maven repository)
  4. change your project build path in eclipse to update the jars versions (the fastest is to search/replace the .classpath file)
  5. re-generate your entities

Thanks again for having reported the problem.

Best regards,
Vincent

List and OneToMany updating

you have used the EntityProxyInterceptor

Yes

 

To test the fix you can :

[...]

OK

Now I get this exception:

     [java] Expression reverseMandatory is undefined on line 314, column 13 in HibernateXdocletEntity.ftl.

In HibernateXdocletEntity.ftl, reverseMandatory is defined only when bidirectional is true. So I tried with:

       <#if bidirectional>
       <#if reverseMandatory>
   *           not-null = "true"
       </#if>
       </#if>

and everything runs fine.

 

 

List and OneToMany updating

You are right. I'll fix it.