Failed insert results in invalid cache state

View: New views
7 Messages — Rating Filter:   Alert me  

Failed insert results in invalid cache state

by bryans :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Here's my situation:

I'm trying to determine if it's possible to retry an insert when it fails due to PK collision. (Please don't ask why, just go with it).

So i've got my own ID Generator which assigns the ID of 1 to every entity. I've also built a ExceptionHandler to deal with the collision error.

In the error handler i've good logic to clone the failed insert object, without the old PK. I update the ID generator's next value to 2. Then i just re-execute the query (which assigns the new ID).

I then execute my tests (JUnit methods), before each of them i delete the database contents to ensure a clean slate:

1) ID Collision insert on single executable statement w/ retry disabled - # Records in DB = 1 - Correct
2) ID Collision insert on single executable statement w/ retry enabled - # Records in DB = 2 - Correct
3) ID Collision on 4th statement after 3 successful inserts (parent, child, grandchild, great-grandchild<-Collision) w/retry disabled - # Of records in DB 0 - Correct

But if i do a "find" on the ID for the parent, i get back the failed inserted object from test #2. The database still has 0 contents because the whole transaction was rolled-back.

Can someone tell me why the cache has failed insert objects in it, and/or how to clear them before i attempt my retries?

Any help is much appreciated, and my thanks in advance.

-B

Re: Failed insert results in invalid cache state

by James Sutherland :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Definitely a tricky thing to do.  Try not cloning the object, as this will violate object identity, just set its id to null and re-execute the query.

What does your error handler do exactly?

I assume you are using JPA/UnitOfWork?


bryans wrote:
Here's my situation:

I'm trying to determine if it's possible to retry an insert when it fails due to PK collision. (Please don't ask why, just go with it).

So i've got my own ID Generator which assigns the ID of 1 to every entity. I've also built a ExceptionHandler to deal with the collision error.

In the error handler i've good logic to clone the failed insert object, without the old PK. I update the ID generator's next value to 2. Then i just re-execute the query (which assigns the new ID).

I then execute my tests (JUnit methods), before each of them i delete the database contents to ensure a clean slate:

1) ID Collision insert on single executable statement w/ retry disabled - # Records in DB = 1 - Correct
2) ID Collision insert on single executable statement w/ retry enabled - # Records in DB = 2 - Correct
3) ID Collision on 4th statement after 3 successful inserts (parent, child, grandchild, great-grandchild<-Collision) w/retry disabled - # Of records in DB 0 - Correct

But if i do a "find" on the ID for the parent, i get back the failed inserted object from test #2. The database still has 0 contents because the whole transaction was rolled-back.

Can someone tell me why the cache has failed insert objects in it, and/or how to clear them before i attempt my retries?

Any help is much appreciated, and my thanks in advance.

-B

Re: Failed insert results in invalid cache state

by bryans :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Here's what I've got in my ExceptionHandler:



     public Object handleException(RuntimeException exception) {

                if (retry && exception instanceof DatabaseException) {
                        DatabaseException dbe = (DatabaseException) exception;
                        if (dbe.getQuery() instanceof InsertObjectQuery) {
                                InsertObjectQuery insert = (InsertObjectQuery) dbe.getQuery();
                                Object dup = insert.getObject();
                                if (dbe.getSession().doesObjectExist(dup)) {
                                        insert.setModifyRow(null);
                                        insert.setPrimaryKey(null);
                                        ObjectBuilder builder = insert.getDescriptor().getObjectBuilder();
                                        ObjectCopyingPolicy policy = new ObjectCopyingPolicy();
                                        policy.setDepth(ObjectCopyingPolicy.CASCADE_ALL_PARTS);
                                        policy.setSession(dbe.getSession());
                                        policy.setShouldResetPrimaryKey(true);
                                        Object clone = builder.copyObject(dup, policy);
                                        //Change the next sequence value
                                        builder.assignSequenceNumber(clone, dbe.getSession());
                                        insert.setObject(clone);
                                        try {
                                                return dbe.getSession().executeQuery(insert);
                                        } catch (Throwable t) {
                                                t.printStackTrace();
                                                throw t;
                                        }
                                }
                        }
                       
                }
                throw exception;
        }



There really isn't any way of setting the ID to null as that would assume that I know the object's class and can cast it. My attempt here is generic to handle any type object, not just predefined ones.

Everything is done via JPA xml specs with Spring's JpaTemplate to handle transaction management logic.

-B

James Sutherland wrote:
Definitely a tricky thing to do.  Try not cloning the object, as this will violate object identity, just set its id to null and re-execute the query.

What does your error handler do exactly?

I assume you are using JPA/UnitOfWork?

Re: Failed insert results in invalid cache state

by James Sutherland :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

This is pretty advanced stuff, so you may want to reconsider you reasons for trying to do this in the first place.  You might want to instead catch the exception in your app and retry the whole transaction, or avoid having duplicate sequence numbers in the first place (EclipseLink will never generate duplicates).

To get your exception handler to work, you can't create a copy, as it violates object identity.  You need to reset the object's pk to null.  You could use the descriptor to do this generically.


Object value = builder.getSequenceMapping().getAttributeValue(null, session);
builder.getSequenceMapping().setAttributeValueInObject(dup, value);



Here's what I've got in my ExceptionHandler:



     public Object handleException(RuntimeException exception) {

                if (retry && exception instanceof DatabaseException) {
                        DatabaseException dbe = (DatabaseException) exception;
                        if (dbe.getQuery() instanceof InsertObjectQuery) {
                                InsertObjectQuery insert = (InsertObjectQuery) dbe.getQuery();
                                Object dup = insert.getObject();
                                if (dbe.getSession().doesObjectExist(dup)) {
                                        insert.setModifyRow(null);
                                        insert.setPrimaryKey(null);
                                        ObjectBuilder builder = insert.getDescriptor().getObjectBuilder();
                                        ObjectCopyingPolicy policy = new ObjectCopyingPolicy();
                                        policy.setDepth(ObjectCopyingPolicy.CASCADE_ALL_PARTS);
                                        policy.setSession(dbe.getSession());
                                        policy.setShouldResetPrimaryKey(true);
                                        Object clone = builder.copyObject(dup, policy);
                                        //Change the next sequence value
                                        builder.assignSequenceNumber(clone, dbe.getSession());
                                        insert.setObject(clone);
                                        try {
                                                return dbe.getSession().executeQuery(insert);
                                        } catch (Throwable t) {
                                                t.printStackTrace();
                                                throw t;
                                        }
                                }
                        }
                       
                }
                throw exception;
        }



There really isn't any way of setting the ID to null as that would assume that I know the object's class and can cast it. My attempt here is generic to handle any type object, not just predefined ones.

Everything is done via JPA xml specs with Spring's JpaTemplate to handle transaction management logic.

-B

James Sutherland wrote:
Definitely a tricky thing to do.  Try not cloning the object, as this will violate object identity, just set its id to null and re-execute the query.

What does your error handler do exactly?

I assume you are using JPA/UnitOfWork?


Re: Failed insert results in invalid cache state

by bryans :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

James,
  This is a response to a previous comment you made. I had to drop this research for projects but now i'm back on it. I wanted to follow up with something you mentioned:

 (EclipseLink will never generate duplicates).

What type of ID generator are you referring to? And, how is the guarantee made (in a clustered environment)? I can only think of a few ways:

1) A database table manages the sequences (select from table, auto-update to next value)
2) EclipseLink validates the value does not already exist as an ID in the table prior to assigning it to the object. However this doesn't guarantee that the ID won't be assigned by another process elsewhere, inserting into the same table.
3) In the above instance, a sequence manager manages state across all instance of EclipseLink in use.



James Sutherland wrote:
This is pretty advanced stuff, so you may want to reconsider you reasons for trying to do this in the first place.  You might want to instead catch the exception in your app and retry the whole transaction, or avoid having duplicate sequence numbers in the first place (EclipseLink will never generate duplicates).

To get your exception handler to work, you can't create a copy, as it violates object identity.  You need to reset the object's pk to null.  You could use the descriptor to do this generically.


Object value = builder.getSequenceMapping().getAttributeValue(null, session);
builder.getSequenceMapping().setAttributeValueInObject(dup, value);

Re: Failed insert results in invalid cache state

by James Sutherland :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

JPA and EclipseLink support three types of sequencing, all three are managed by the database, so never generate duplicates.  TABLE uses a sequence table in the database, database transaction prevent duplicates, IDENTITY uses IDENTITY columns in the database, SEQUENCE uses SEQUENCE objects in the database.



James,
  This is a response to a previous comment you made. I had to drop this research for projects but now i'm back on it. I wanted to follow up with something you mentioned:

 (EclipseLink will never generate duplicates).

What type of ID generator are you referring to? And, how is the guarantee made (in a clustered environment)? I can only think of a few ways:

1) A database table manages the sequences (select from table, auto-update to next value)
2) EclipseLink validates the value does not already exist as an ID in the table prior to assigning it to the object. However this doesn't guarantee that the ID won't be assigned by another process elsewhere, inserting into the same table.
3) In the above instance, a sequence manager manages state across all instance of EclipseLink in use.



James Sutherland wrote:
This is pretty advanced stuff, so you may want to reconsider you reasons for trying to do this in the first place.  You might want to instead catch the exception in your app and retry the whole transaction, or avoid having duplicate sequence numbers in the first place (EclipseLink will never generate duplicates).

To get your exception handler to work, you can't create a copy, as it violates object identity.  You need to reset the object's pk to null.  You could use the descriptor to do this generically.


Object value = builder.getSequenceMapping().getAttributeValue(null, session);
builder.getSequenceMapping().setAttributeValueInObject(dup, value);


Re: Failed insert results in invalid cache state

by bryans :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Ah! I thought you meant that EclipseLink had its own mechanism for preventing duplicates. We have been precluded from using any of the implementation items you have mentioned.

Thanks for your help!

-B

James Sutherland wrote:
JPA and EclipseLink support three types of sequencing, all three are managed by the database, so never generate duplicates.  TABLE uses a sequence table in the database, database transaction prevent duplicates, IDENTITY uses IDENTITY columns in the database, SEQUENCE uses SEQUENCE objects in the database.