Sure, here’s the relevant parts:
<db-entity name="company" schema="public">
<db-attribute name="name" type="VARCHAR" length="50”/>
<db-attribute name="uuid" type="OTHER" isPrimaryKey="true"
isMandatory="true" length="2147483647"/>
</db-entity>
<db-entity name="entity" schema="public”>
<db-attribute name="audit_uuid" type="OTHER" length="2147483647”/>
<db-attribute name="reference" type="VARCHAR" length="30"/>
<db-attribute name="type" type="CHAR" isMandatory="true" length="1”/>
<db-attribute name="uuid" type="OTHER" isPrimaryKey="true"
isMandatory="true" length="2147483647”/>
<db-attribute name="parent_entity_uuid" type="OTHER"
length="2147483647"/>
</db-entity>
<obj-entity name="Company" superEntityName="Entity"
className=“...data.models.Company”>
<qualifier><![CDATA[type = "C"]]></qualifier>
<obj-attribute name="name" type="java.lang.String"
db-attribute-path="company.name”/>
</obj-entity>
<obj-entity name="Entity" abstract="true" className=“...data.models.Entity"
dbEntityName="entity" superClassName=“...data.CayenneBaseDataObject">
<obj-attribute name="reference" type="java.lang.String"
db-attribute-path="reference”/>
<obj-attribute name="type" type=“...data.util.EntityType"
db-attribute-path="type”/>
</obj-entity>
<db-relationship name="entity" source="company" target="entity" toMany="false”>
<db-attribute-pair source="uuid" target="uuid"/>
</db-relationship>
<db-relationship name="company" source="entity" target="company"
toDependentPK="true" toMany="false">
<db-attribute-pair source="uuid" target="uuid"/>
</db-relationship>
<db-relationship name="parentEntity" source="entity" target="entity"
toMany="false”>
<db-attribute-pair source="parent_entity_uuid" target="uuid”/>
</db-relationship>
<db-relationship name="subEntities" source="entity" target="entity"
toMany="true”>
<db-attribute-pair source="uuid" target="parent_entity_uuid”/>
</db-relationship>
<obj-relationship name="parentCompany" source="Company" target="Company"
deleteRule="Nullify" db-relationship-path="parentEntity"/>
<obj-relationship name="subCompanies" source="Company" target="Company"
deleteRule="Deny" db-relationship-path="subEntities"/>
I’ve created a fix for this issue in DataDomainDBDiffBuilder.appendForeignKeys
on line 133 (within the nested loop):
String joinSourceName = join.getSourceName();
if (dbDiff.get(joinSourceName) == null && dbEntity.getAttribute(joinSourceName)
!= null) {
dbDiff.put(joinSourceName, value);
}
The only change is that I’m checking if the dbEntity actually has the attribute
it’s trying to add from the join. If it doesn’t have the attribute, then it
doesn’t make sense to put it on the dbDiff. In
DataDomainUpdateBucket.updatedAttribtues (mentioned in my prior message), it
expects everything in the dbDiff to be an attribute that exists on the
dbEntity. The fix passed Cayenne’s tests and didn’t appear to cause any side
effects. Of course, this is all moot if I just botched the data map. Let me
know what you think.
Thanks,
Doug
> On Jul 28, 2016, at 3:47 AM, Andrus Adamchik <[email protected]> wrote:
>
> Would you mind posting a relevant part of your DataMap XML?
>
> Andrus
>
>> On Jul 13, 2016, at 8:40 PM, Dougan Stuart <[email protected]> wrote:
>>
>> I have a class Company with a one-to-many relationship to other Companies
>> (parentCompany <- subCompanies). I’m able to set a Company’s parent when
>> creating one, but upon update I’m getting a NullPointerException in
>> DefaultQuotingStrategy.quotedName because it’s been sent a null attribute.
>> Company extends the abstract class Entity; on the DB side Entity and Company
>> have a one-to-one relationship, and Entity has a parentEntity reference
>> (which is modeled as parentCompany). The obj-relationship parentCompany is
>> set up with the target Company and path parentEntity.
>>
>> The null attribute is being added in
>> org.apache.cayenne.access.DataDomainUpdateBucket.updatedAttributes. The
>> method is called first with the dbEntity Entity and the an updatedSnapshot
>> containing parentEntity, which is what I would expect. However, after this
>> updatedAttributes is called with Company and an updatedSnapshot containing
>> parentEntity, so the following line
>>
>> attributes.add(entityAttributes.get(name))
>>
>> will return a null attribute when trying to get the name “parentEntity” off
>> Company’s attributes; that attribute only exists on Entity. I’d appreciate
>> any help on preventing it from trying to add invalid attributes.
>>
>> I’m not sure if this is the root cause, but
>> DataDomainUpdateBucket.appendQueriesInternal calls updatedAttributes based
>> off the bucket’s dbEntities, which in this case are set in
>> DataDomainSyncBucket.groupObjEntitiesBySpannedDbEntities; this method is
>> only adding Company as a “secondary DbEntity” (as referred to in the
>> comments) because Company has a flattened attribute “name" that does not
>> exist on the Entity.
>>
>> Here’s the stack trace:
>>
>> java.lang.NullPointerException
>> at
>> org.apache.cayenne.dba.DefaultQuotingStrategy.quotedName(DefaultQuotingStrategy.java:62)
>> at
>> org.apache.cayenne.access.translator.batch.UpdateBatchTranslator.createSql(UpdateBatchTranslator.java:60)
>> at
>> org.apache.cayenne.access.translator.batch.DefaultBatchTranslator.ensureTranslated(DefaultBatchTranslator.java:52)
>> at
>> org.apache.cayenne.access.translator.batch.DefaultBatchTranslator.getSql(DefaultBatchTranslator.java:64)
>> at
>> org.apache.cayenne.access.jdbc.BatchAction.runAsBatch(BatchAction.java:103)
>> at
>> org.apache.cayenne.access.jdbc.BatchAction.performAction(BatchAction.java:90)
>> at
>> org.apache.cayenne.access.DataNodeQueryAction.runQuery(DataNodeQueryAction.java:97)
>> at org.apache.cayenne.access.DataNode.performQueries(DataNode.java:293)
>> at
>> org.apache.cayenne.access.DataDomainFlushAction.runQueries(DataDomainFlushAction.java:233)
>> at
>> org.apache.cayenne.access.DataDomainFlushAction.flush(DataDomainFlushAction.java:154)
>> at org.apache.cayenne.access.DataDomain.onSyncFlush(DataDomain.java:693)
>> at org.apache.cayenne.access.DataDomain$2.transform(DataDomain.java:659)
>> at
>> org.apache.cayenne.access.DataDomain.runInTransaction(DataDomain.java:720)
>> at
>> org.apache.cayenne.access.DataDomain.onSyncNoFilters(DataDomain.java:655)
>> at
>> org.apache.cayenne.access.DataDomain$DataDomainSyncFilterChain.onSync(DataDomain.java:863)
>> at org.apache.cayenne.access.DataDomain.onSync(DataDomain.java:636)
>> at
>> org.apache.cayenne.access.DataContext.flushToParent(DataContext.java:727)
>> at
>> org.apache.cayenne.access.DataContext.commitChanges(DataContext.java:676)
>>
>> Thanks,
>> Doug
>>
>