I'm running into a problem that I hope someone has some insight into. Our application is currently setting the id programatically in our base domain object's constructor. The mapping for the domain object, which every other object extends, defines the ID as assigned in the mapping file. We then set the ID in the constructor.
So my problem occurs when I have a new object graph that I'm trying to persist all at once. So in this case, I've got a Person object that has a list of contacts (phone, address, etc). When I save Person, the logs show that it's performing an insert into the Person table (which is correct), but when it gets to Phone, it's performing an update instead of an insert. Is this because the ID is set, so hibernate thinks that the object isn't transient?
I'm using hibernate version 3.0.5.
Here's my mapping:
Code:
<class name="com.tickets.common.domain.DomainObject" table="DOMAIN_OBJECT" dynamic-insert="false"
dynamic-update="false" abstract="true">
<id name="id" type="java.lang.String" unsaved-value="null">
<column name="ID" sql-type="CHARACTER VARYING(22)" />
<generator class="assigned">
</generator>
</id>
<property name="owningId" type="java.lang.String">
<column name="OWNING_ID" not-null="false" unique="false" sql-type="CHARACTER VARYING(22)" />
</property>
</class>
<union-subclass name="com.tickets.patron.domain.Party" extends="com.tickets.common.domain.TraitableDomainObject"
table="PARTY" dynamic-insert="false" dynamic-update="false" abstract="true">
<property name="phonetics" type="java.lang.String">
<column name="PHONETICS" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="formattedName" type="java.lang.String">
<column name="FORMATTED_NAME" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="formatOverride" type="boolean">
<column name="FORMAT_OVERRIDE" not-null="false" unique="false" sql-type="BOOLEAN" />
</property>
<property name="active" type="boolean">
<column name="ACTIVE" not-null="true" unique="false" sql-type="BOOLEAN" />
</property>
<property name="name" type="java.lang.String">
<column name="NAME" not-null="true" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<set name="creditCards" order-by="PARTY_ID" lazy="true" fetch="select" inverse="true"
cascade="save-update,delete">
<key foreign-key="CREDIT_CARD_PARTY_IDC">
<column name="PARTY_ID" sql-type="CHARACTER VARYING(22)" />
</key>
<one-to-many class="com.tickets.common.domain.CreditCard" />
</set>
<bag name="contacts" order-by="PARTY_ID" lazy="true" fetch="select" inverse="true" cascade="save-update,delete">
<key foreign-key="CONTACT_PARTY_IDC">
<column name="PARTY_ID" sql-type="CHARACTER VARYING(22)" />
</key>
<one-to-many class="com.tickets.common.domain.Contact" />
</bag>
<set name="notes" table="PARTY_CORRESPONDENCE" order-by="PARTY_ID" lazy="true" fetch="select" inverse="false"
cascade="save-update,delete">
<key foreign-key="CORRESPONDENCE_PARTY_IDC">
<column name="PARTY_ID" sql-type="CHARACTER VARYING(22)" />
</key>
<many-to-many class="com.tickets.common.domain.Correspondence" column="NOTES_ID" unique="true" />
</set>
<many-to-one name="primaryPhone" class="com.tickets.common.domain.Phone" cascade="save-update,delete"
foreign-key="PARTY_PRIMARY_PHONE_IDC" lazy="false" fetch="select">
<column name="PRIMARY_PHONE_ID" not-null="false" sql-type="CHARACTER VARYING(22)" />
</many-to-one>
<many-to-one name="primaryAddress" class="com.tickets.common.domain.Address" cascade="save-update,delete"
foreign-key="PARTY_PRIMARY_ADDRESS_IDC" lazy="false" fetch="select">
<column name="PRIMARY_ADDRESS_ID" not-null="false" sql-type="CHARACTER VARYING(22)" />
</many-to-one>
<many-to-one name="primaryEmail" class="com.tickets.common.domain.Email" cascade="save-update,delete"
foreign-key="PARTY_PRIMARY_EMAIL_IDC" lazy="false" fetch="select">
<column name="PRIMARY_EMAIL_ID" not-null="false" sql-type="CHARACTER VARYING(22)" />
</many-to-one>
<many-to-one name="primaryCreditCard" class="com.tickets.common.domain.CreditCard"
foreign-key="PARTY_PRIMARY_CREDIT_CARD_IDC" lazy="false" fetch="select">
<column name="PRIMARY_CREDIT_CARD_ID" not-null="false" sql-type="CHARACTER VARYING(22)" />
</many-to-one>
<set name="linkedPatrons" table="PARTY_PATRONLINK" order-by="PARTY_ID" lazy="true" fetch="select"
inverse="false">
<key foreign-key="PATRON_LINK_PARTY_IDC">
<column name="PARTY_ID" sql-type="CHARACTER VARYING(22)" />
</key>
<many-to-many class="com.tickets.patron.domain.PatronLink" column="LINKED_PATRONS_ID" unique="true" />
</set>
<many-to-one name="subordinate" class="com.tickets.patron.domain.Party" foreign-key="PARTY_SUBORDINATE_IDC"
lazy="proxy" fetch="select">
<column name="SUBORDINATE_ID" not-null="false" sql-type="CHARACTER VARYING(22)" />
</many-to-one>
<set name="accounts" table="ACCOUNT_PARTY" order-by="ACCOUNTS_ID" lazy="true" fetch="select" inverse="true">
<key foreign-key="ACCOUNT_OWNERS_IDC">
<column name="OWNERS_ID" sql-type="CHARACTER VARYING(22)" />
</key>
<many-to-many class="com.tickets.account.domain.Account" foreign-key="PARTY_ACCOUNTS_IDC">
<column name="ACCOUNTS_ID" sql-type="CHARACTER VARYING(22)" />
</many-to-many>
</set>
</union-subclass>
<union-subclass name="com.tickets.patron.domain.Person" extends="com.tickets.patron.domain.Party" table="PERSON"
dynamic-insert="false" dynamic-update="false" abstract="false">
<property name="firstName" type="java.lang.String">
<column name="FIRST_NAME" not-null="true" unique="false" sql-type="CHARACTER VARYING(50)" />
</property>
<property name="middleName" type="java.lang.String">
<column name="MIDDLE_NAME" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="lastName" type="java.lang.String">
<column name="LAST_NAME" not-null="true" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="formalSalutation" type="java.lang.String">
<column name="FORMAL_SALUTATION" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="salutationOverride" type="boolean">
<column name="SALUTATION_OVERRIDE" not-null="false" unique="false" sql-type="BOOLEAN" />
</property>
<property name="namePrefix" type="java.lang.String">
<column name="NAME_PREFIX" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="nameSuffix" type="java.lang.String">
<column name="NAME_SUFFIX" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
</union-subclass>
<union-subclass name="com.tickets.common.domain.Phone" extends="com.tickets.common.domain.Contact" table="PHONE"
dynamic-insert="false" dynamic-update="false" abstract="false">
<property name="countryCode" type="java.lang.String">
<column name="COUNTRY_CODE" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="areaCode" type="java.lang.String">
<column name="AREA_CODE" not-null="true" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="localNumber" type="java.lang.String">
<column name="LOCAL_NUMBER" not-null="true" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="extension" type="java.lang.String">
<column name="EXTENSION" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="phoneDisplay" type="java.lang.String">
<column name="PHONE_DISPLAY" not-null="false" unique="false" sql-type="CHARACTER VARYING(1024)" />
</property>
<property name="telemarket" type="boolean">
<column name="TELEMARKET" not-null="false" unique="false" sql-type="BOOLEAN" />
</property>
</union-subclass>
Regarding my source code, my pojo's are really straight forward. I'll ommit those for the sake of keeping this post short. But when I go to persist the object, I call session.save().
Here's the log output:
Code:
[org.hibernate.engine.ActionQueue]changes must be flushed to space: PERSON
[org.hibernate.event.def.DefaultAutoFlushEventListener]Need to execute flush
[org.hibernate.event.def.AbstractFlushingEventListener]executing flush
[org.hibernate.persister.entity.BasicEntityPersister]Inserting entity: [com.tickets.patron.domain.Person#pFtJDsKBdKLv6AhDMHUNa]
[org.hibernate.jdbc.AbstractBatcher]about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
[org.hibernate.SQL]insert into PERSON (OWNING_ID, PHONETICS, FORMATTED_NAME, FORMAT_OVERRIDE, ACTIVE, NAME, PRIMARY_PHONE_ID, PRIMARY_ADDRESS_ID, PRIMARY_EMAIL_ID, PRIMARY_CREDIT_CARD_ID, SUBORDINATE_ID, FIRST_NAME, MIDDLE_NAME, LAST_NAME, FORMAL_SALUTATION, SALUTATION_OVERRIDE, NAME_PREFIX, NAME_SUFFIX, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
[org.hibernate.jdbc.AbstractBatcher]preparing statement
[org.hibernate.persister.entity.BasicEntityPersister]Dehydrating entity: [com.tickets.patron.domain.Person#pFtJDsKBdKLv6AhDMHUNa]
[org.hibernate.type.StringType]binding null to parameter: 1
[org.hibernate.type.StringType]binding 'JNT' to parameter: 2
[org.hibernate.type.StringType]binding 'Mr. John Doe' to parameter: 3
[org.hibernate.type.BooleanType]binding 'false' to parameter: 4
[org.hibernate.type.BooleanType]binding 'false' to parameter: 5
[org.hibernate.type.StringType]binding 'Doe, John' to parameter: 6
[org.hibernate.type.StringType]binding null to parameter: 7
[org.hibernate.type.StringType]binding null to parameter: 8
[org.hibernate.type.StringType]binding null to parameter: 9
[org.hibernate.type.StringType]binding null to parameter: 10
[org.hibernate.type.StringType]binding null to parameter: 11
[org.hibernate.type.StringType]binding 'John' to parameter: 12
[org.hibernate.type.StringType]binding null to parameter: 13
[org.hibernate.type.StringType]binding 'Doe' to parameter: 14
[org.hibernate.type.StringType]binding null to parameter: 15
[org.hibernate.type.BooleanType]binding 'false' to parameter: 16
[org.hibernate.type.StringType]binding 'Mr.' to parameter: 17
[org.hibernate.type.StringType]binding null to parameter: 18
[org.hibernate.type.StringType]binding 'pFtJDsKBdKLv6AhDMHUNa' to parameter: 19
[org.hibernate.jdbc.AbstractBatcher]Adding to batch
[org.hibernate.persister.entity.BasicEntityPersister]Inserting entity: [com.tickets.patron.domain.Organization#pYmxmKBhDu0MHgrBWgL48]
[org.hibernate.jdbc.AbstractBatcher]Executing batch size: 1
[org.hibernate.jdbc.AbstractBatcher]about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
[org.hibernate.jdbc.AbstractBatcher]closing statement
[org.hibernate.jdbc.AbstractBatcher]about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
[org.hibernate.SQL]insert into ORGANIZATION (OWNING_ID, PHONETICS, FORMATTED_NAME, FORMAT_OVERRIDE, ACTIVE, NAME, PRIMARY_PHONE_ID, PRIMARY_ADDRESS_ID, PRIMARY_EMAIL_ID, PRIMARY_CREDIT_CARD_ID, SUBORDINATE_ID, PARENT_ID, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
[org.hibernate.jdbc.AbstractBatcher]preparing statement
[org.hibernate.persister.entity.BasicEntityPersister]Dehydrating entity: [com.tickets.patron.domain.Organization#pYmxmKBhDu0MHgrBWgL48]
[org.hibernate.type.StringType]binding null to parameter: 1
[org.hibernate.type.StringType]binding 'TSKN' to parameter: 2
[org.hibernate.type.StringType]binding 'Doe's Consulting Firm' to parameter: 3
[org.hibernate.type.BooleanType]binding 'false' to parameter: 4
[org.hibernate.type.BooleanType]binding 'false' to parameter: 5
[org.hibernate.type.StringType]binding 'Doe's Consulting Firm' to parameter: 6
[org.hibernate.type.StringType]binding null to parameter: 7
[org.hibernate.type.StringType]binding null to parameter: 8
[org.hibernate.type.StringType]binding null to parameter: 9
[org.hibernate.type.StringType]binding null to parameter: 10
[org.hibernate.type.StringType]binding null to parameter: 11
[org.hibernate.type.StringType]binding null to parameter: 12
[org.hibernate.type.StringType]binding 'pYmxmKBhDu0MHgrBWgL48' to parameter: 13
[org.hibernate.jdbc.AbstractBatcher]Adding to batch
[org.hibernate.jdbc.AbstractBatcher]Executing batch size: 1
[org.hibernate.jdbc.AbstractBatcher]about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
[org.hibernate.jdbc.AbstractBatcher]closing statement
[org.hibernate.persister.entity.BasicEntityPersister]Updating entity: [com.tickets.common.domain.Phone#pFtJFXMdvTqFVicoUC7gc]
[org.hibernate.jdbc.AbstractBatcher]about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
[org.hibernate.SQL]update PHONE set OWNING_ID=?, USAGE_TYPE_ID=?, PARTY_ID=?, COUNTRY_CODE=?, AREA_CODE=?, LOCAL_NUMBER=?, EXTENSION=?, PHONE_DISPLAY=?, TELEMARKET=?, ADDRESS_ID=? where ID=?
[org.hibernate.jdbc.AbstractBatcher]preparing statement
[org.hibernate.persister.entity.BasicEntityPersister]Dehydrating entity: [com.tickets.common.domain.Phone#pFtJFXMdvTqFVicoUC7gc]
[org.hibernate.type.StringType]binding null to parameter: 1
[org.hibernate.type.StringType]binding 'pFtJDsKBdKLv6AhDMHUNa' to parameter: 3
[org.hibernate.type.StringType]binding null to parameter: 4
[org.hibernate.type.StringType]binding '714' to parameter: 5
[org.hibernate.type.StringType]binding '327-5400' to parameter: 6
[org.hibernate.type.StringType]binding null to parameter: 7
[org.hibernate.type.StringType]binding null to parameter: 8
[org.hibernate.type.BooleanType]binding 'false' to parameter: 9
[org.hibernate.type.StringType]binding 'pGaXpnG5cUudY1piN6jUU' to parameter: 10
[org.hibernate.type.StringType]binding 'pFtJFXMdvTqFVicoUC7gc' to parameter: 11
[org.hibernate.jdbc.AbstractBatcher]Adding to batch
[org.hibernate.persister.entity.BasicEntityPersister]Updating entity: [com.tickets.common.domain.Address#pGaXpnG5cUudY1piN6jUU]
[org.hibernate.jdbc.AbstractBatcher]Executing batch size: 1
[org.hibernate.jdbc.AbstractBatcher]Exception executing batch:
org.hibernate.StaleStateException: Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
at org.hibernate.jdbc.BatchingBatcher.checkRowCount(BatchingBatcher.java:92)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:78)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:57)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:174)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:74)
Notice the call to update phone instead of actually inserting it. Now I could create the phone object before persisting the person, but that's not the behavior I'm striving for. I want to be able to persist the whole graph all at once.
Can anyone shed some light if this is a problem? Or if this is a common problem, what are some possible solutions?
Thanks in advance.
John