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, "contact
design2see [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 :
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 week
). 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 :
and reference this new Event.employee property descriptor in the Employee entity :
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 :
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.