cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Bryan Lewis <jbryanle...@gmail.com>
Subject Re: possible bug with inherited cayenne objects and mandatory fields
Date Thu, 17 Feb 2011 13:29:30 GMT
This sounds like the same problem we've lived with for years.  In a
complicated model (hundreds of entities for us), Cayenne's graph-following
algorithm (AshwoodEntitySorter) doesn't have enough information to perform
all the commits in exactly the right order.  (Perhaps this is an argument
for supporting database constraints in the modeler, so that Cayenne would
know about them.)

If multiple interrelated entities are inserted or deleted in the same
commit, they can occasionally get committed out of order.  This matters if
your database is fully constrained.  (For us the error usually involves a
foreign-key constraint.  I've wondered why other people weren't asking about
this problem more often, and assumed that most people didn't have such large
models, or seldom did multi-entiy commits, or didn't add constraints to the
database.)

Anyway, the fix is to override the EntitySorter and explicitly tell it the
order in each case that arises.


    // We call this to initialize our model... the first time a DataContext
is created.
    private static synchronized Configuration initConfiguration()
    {
        ...
        DataDomain domain = config.getDomain();
        // We have only one DataNode.
        DataNode node = domain.getDataNodes().iterator().next();

        node.setEntitySorter(new CustomEntitySorter());
        ...
    }

public class CustomEntitySorter extends AshwoodEntitySorter
{
    public CustomEntitySorter()
    {
        super(Collections.EMPTY_LIST);
    }

    @Override
    public void sortDbEntities(List<DbEntity> dbEntities, boolean
deleteOrder)
    {
        // Somewhat ugly code, depending on Cayenne internals.
        _indexSorter();
        Collections.sort(dbEntities, getDbEntityComparator(deleteOrder));

        // No need to check the ordering of a single-entity list.
        if (dbEntities.size() <= 1) {
            return;
        }

        // Two examples.  In our apps we have a few dozen of these lines.
        putParentBeforeChild("contact", "note", dbEntities, deleteOrder);
        putParentBeforeChild("company", "companyphone", dbEntities,
deleteOrder);
    }

    /**
     * If the parent object comes after the child in the list, move it
before
     * the child.  Unless we're deleting, which reverses the order.
     *
     * The naming might be a bit confusing.  I'm calling the parent the
     * entity that appears to be the source of the relationship in the
modeler.
     * The parent record needs to be inserted first.
     *
     * The confusing thing is, the parent table is the one pointed *to* by
     * the FK constraint, and the child is the table that defines the
constraint.
     */
    private void putParentBeforeChild(String parentDbEntityName,
                                      String childDbEntityName,
                                      List<DbEntity> dbEntities,
                                      boolean deleteOrder)
    {
        int iParent = -1;
        int iChild = -1;

        // Protect against a misspelling of an entity name, which I did
once.
        List<String> allDbEntityNames =
Model.getInstance().getDbEntityNames();
        if (!allDbEntityNames.contains(parentDbEntityName)) {
            log.error("!! Unrecognized parentDbEntity name " +
parentDbEntityName);
        }
        if (!allDbEntityNames.contains(childDbEntityName)) {
            log.error("!! Unrecognized childDbEntity name " +
childDbEntityName);
        }

        for (int i = 0; i < dbEntities.size(); i++) {
            DbEntity entity = dbEntities.get(i);
            String name = entity.getName();
            // Use equalsIgnoreCase in case we're switching between Oracle
and Postgres databases.
            if (name.equalsIgnoreCase(parentDbEntityName) && iParent == -1)
{
                iParent = i;
            }
            // Check for -1 so that we get the lowest child in case there
            // are more than one.  Might not ever matter.
            if (name.equalsIgnoreCase(childDbEntityName) && iChild == -1) {
                iChild = i;
            }
        }

        if (iParent != -1 && iChild != -1) {
            StringBuilder buf = new StringBuilder();
            if (deleteOrder) {
                if (iParent < iChild) {
                    buf.append("-- need to move
").append(childDbEntityName);
                    buf.append(" before ").append(parentDbEntityName);
                    buf.append(" for deletion");
                    log.debug(buf.toString());

                    // Remove and re-add it just before the parent.
                    DbEntity childEntity = dbEntities.remove(iChild);
                    dbEntities.add(iParent, childEntity);
                }
            }
            else {
                if (iParent > iChild) {
                    buf.append("-- need to move
").append(parentDbEntityName);
                    buf.append(" before ").append(childDbEntityName);
                    buf.append(" for insertion");
                    log.debug(buf.toString());

                    // Remove and re-add it just before the child.
                    DbEntity parentEntity = dbEntities.remove(iParent);
                    dbEntities.add(iChild, parentEntity);
                }
            }
        }
    }
}



On Thu, Feb 17, 2011 at 3:11 AM, Aristedes Maniatis <ari@maniatis.org>wrote:

> On 15/02/11 2:04 PM, Marcin Skladaniec wrote:
>
>> java.sql.SQLIntegrityConstraintViolationException: The statement was
>> aborted because it would have caused a duplicate key value in a unique or
>> primary key constraint or unique index identified by 'TAGRELATION_UNIQUE'
>> defined on 'TAGRELATION'.
>>
>
>
> Can I summarise, since I think you've highlighted a problem which is
> completely different to your email subject.
>
> 1. Table A has primary key which is user defined (in this case, it is the
> join between two other tables, so the PK is the compound of the two foreign
> keys)
>
> 2. Create record in table A. Commit.
>
> 3. Delete that record in table A.
>
> 4. Create another record in table A with the same PK as the one just
> deleted (since it joins the same other two tables).
>
> 5. Commit -> ERRROR
>
>
> The create in step 4 is being committed to the db before the delete in step
> 3, failing the key constraint.
>
>
> Use case
> --------
> You may wonder why on earth this is happening. Well, in a rich client
> application a user might tick a checkbox to link two records and then untick
> it and tick it again while they make up their mind. All those changes result
> in changes to the context.
>
>
> Solution
> --------
>
> A. Dedupe the overlapping create/delete records in the context before
> committing.
> B. Order the SQL better.
>
>
>
> Any thoughts about how Cayenne could deal with this better? What do other
> ORMs do in this scenario?
>
>
>
> Ari
>
>
> --
> -------------------------->
> Aristedes Maniatis
> GPG fingerprint CBFB 84B4 738D 4E87 5E5C  5EFA EF6A 7D2E 3E49 102A
>

Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message