[#1547] Support "optimistic locking" in UpdatableRecord.storeLocked()

- Added UpdatableRecord.deleteLocked()
This commit is contained in:
Lukas Eder 2012-07-15 15:08:12 +02:00
parent b5e7029c60
commit 3f85fb95cd
5 changed files with 122 additions and 9 deletions

View File

@ -633,7 +633,7 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
R record5 = create().fetchOne(table, id.equal(1));
// Delete the book
assertEquals(1, record4.delete());
assertEquals(1, record4.deleteLocked());
// Storing without changing shouldn't execute any queries
assertEquals(0, record5.storeLocked());
@ -650,5 +650,23 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658,
assertEquals(1, record4.storeLocked());
assertEquals(1, record5.storeLocked());
assertEquals("New Title 5", create().fetchOne(table, id.equal(1)).getValue(string));
// Deleting the original should no longer be possible
try {
record4.deleteLocked();
fail();
}
catch (DataChangedException expected) {}
// Refreshing and deleting should work
record4.refresh();
assertEquals(1, record4.deleteLocked());
// Now the other record cannot be deleted anymore
try {
record5.deleteLocked();
fail();
}
catch (DataChangedException expected) {}
}
}

View File

@ -113,8 +113,8 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
* Store this record back to the database assuming an optimistic lock.
* <p>
* This performs the same action as {@link #storeUsing(TableField...)},
* except that if an <code>UPDATE</code> is performed, this record will be
* compared with the latest state in the database.
* except that if an <code>UPDATE</code> is performed, this record will
* first be compared with the latest state in the database.
* <p>
* Note that in order to compare this record with the latest state, the
* database record will be locked pessimistically using a
@ -165,6 +165,44 @@ public interface TableRecord<R extends TableRecord<R>> extends Record {
*/
int deleteUsing(TableField<R, ?>... keys) throws DataAccessException;
/**
* Deletes this record from the database assuming an optimistic lock.
* <p>
* This performs the same action as {@link #deleteUsing(TableField...)},
* except that this record will first be compared with the latest state in
* the database.
* <p>
* Note that in order to compare this record with the latest state, the
* database record will be locked pessimistically using a
* <code>SELECT .. FOR UPDATE</code> statement. Not all databases support
* the <code>FOR UPDATE</code> clause natively. Namely, the following
* databases will show slightly different behaviour:
* <ul>
* <li> {@link SQLDialect#CUBRID} and {@link SQLDialect#SQLSERVER}: jOOQ will
* try to lock the database record using JDBC's
* {@link ResultSet#TYPE_SCROLL_SENSITIVE} and
* {@link ResultSet#CONCUR_UPDATABLE}.</li>
* <li> {@link SQLDialect#SQLITE}: No pessimistic locking is possible. Client
* code must assure that no race-conditions can occur between jOOQ's
* checking of database record state and the actual <code>UPDATE</code></li>
* </ul>
* <p>
* See {@link LockProvider#setForUpdate(boolean)} for more details
* <p>
* Unlike {@link #deleteUsing(TableField...)}, this will fail if several
* records are concerned.
*
* @param keys The key fields for the <code>DELETE</code> statement's
* <code>WHERE</code> clause.
* @return The number of deleted records.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException if the record has already been changed in
* the database
* @see #deleteUsing(TableField...)
* @see LockProvider#setForUpdate(boolean)
*/
int deleteLockedUsing(TableField<R, ?>... keys) throws DataAccessException, DataChangedException;
/**
* Refresh this record from the database, based on the value of the provided
* keys.

View File

@ -140,8 +140,8 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
* Store this record back to the database assuming an optimistic lock.
* <p>
* This performs the same action as {@link #store()}, except that if an
* <code>UPDATE</code> is performed, this record will be compared with the
* latest state in the database.
* <code>UPDATE</code> is performed, this record will first be compared with
* the latest state in the database.
* <p>
* Note that in order to compare this record with the latest state, the
* database record will be locked pessimistically using a
@ -163,8 +163,8 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
* @return <code>1</code> if the record was stored to the database. <code>0
* </code> if storing was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException if the record has already been changed in
* the database
* @throws DataChangedException if the record has already been
* changed/deleted in the database
* @see #store()
* @see #storeLockedUsing(TableField...)
* @see LockProvider#setForUpdate(boolean)
@ -189,6 +189,40 @@ public interface UpdatableRecord<R extends UpdatableRecord<R>> extends Updatable
*/
int delete() throws DataAccessException;
/**
* Deletes this record from the database assuming an optimistic lock.
* <p>
* This performs the same action as {@link #delete()}, except that this
* record will first be compared with the latest state in the database.
* <p>
* Note that in order to compare this record with the latest state, the
* database record will be locked pessimistically using a
* <code>SELECT .. FOR UPDATE</code> statement. Not all databases support
* the <code>FOR UPDATE</code> clause natively. Namely, the following
* databases will show slightly different behaviour:
* <ul>
* <li> {@link SQLDialect#CUBRID} and {@link SQLDialect#SQLSERVER}: jOOQ will
* try to lock the database record using JDBC's
* {@link ResultSet#TYPE_SCROLL_SENSITIVE} and
* {@link ResultSet#CONCUR_UPDATABLE}.</li>
* <li> {@link SQLDialect#SQLITE}: No pessimistic locking is possible. Client
* code must assure that no race-conditions can occur between jOOQ's
* checking of database record state and the actual <code>UPDATE</code></li>
* </ul>
* <p>
* See {@link LockProvider#setForUpdate(boolean)} for more details
*
* @return <code>1</code> if the record was deleted from the database.
* <code>0</code> if deletion was not necessary.
* @throws DataAccessException if something went wrong executing the query
* @throws DataChangedException if the record has already been
* changed/deleted in the database
* @see #delete()
* @see #deleteUsing(TableField...)
* @see LockProvider#setForUpdate(boolean)
*/
int deleteLocked() throws DataAccessException, DataChangedException;
/**
* Refresh this record from the database, based on the value of the primary
* key or main unique key.

View File

@ -101,7 +101,7 @@ public class TableRecordImpl<R extends TableRecord<R>> extends TypeRecord<Table<
return storeUsing0(keys, true);
}
private final int storeUsing0(TableField<R, ?>[] keys, boolean checkLocked) {
private final int storeUsing0(TableField<R, ?>[] keys, boolean checkIfChanged) {
boolean executeUpdate = false;
for (TableField<R, ?> field : keys) {
@ -121,7 +121,7 @@ public class TableRecordImpl<R extends TableRecord<R>> extends TypeRecord<Table<
int result = 0;
if (executeUpdate) {
result = storeUpdate(keys, checkLocked);
result = storeUpdate(keys, checkIfChanged);
}
else {
result = storeInsert();
@ -237,6 +237,15 @@ public class TableRecordImpl<R extends TableRecord<R>> extends TypeRecord<Table<
@Override
public final int deleteUsing(TableField<R, ?>... keys) {
return deleteUsing0(keys, false);
}
@Override
public final int deleteLockedUsing(TableField<R, ?>... keys) {
return deleteUsing0(keys, true);
}
private int deleteUsing0(TableField<R, ?>[] keys, boolean checkIfChanged) {
try {
DeleteQuery<R> delete = create().deleteQuery(getTable());
@ -244,6 +253,13 @@ public class TableRecordImpl<R extends TableRecord<R>> extends TypeRecord<Table<
addCondition(delete, field);
}
// [#1547] If optimistic locking checks are requested, try fetching the
// Record again first, and compare this Record's original values with
// the ones in the database
if (checkIfChanged && delete.isExecutable()) {
checkIfChanged(keys);
}
return delete.execute();
}

View File

@ -42,6 +42,8 @@ import org.jooq.Record;
import org.jooq.UniqueKey;
import org.jooq.UpdatableRecord;
import org.jooq.UpdatableTable;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataChangedException;
/**
* A record implementation for a record holding a primary key
@ -86,6 +88,11 @@ public class UpdatableRecordImpl<R extends UpdatableRecord<R>> extends TableReco
return deleteUsing(getMainKey().getFieldsArray());
}
@Override
public final int deleteLocked() throws DataAccessException, DataChangedException {
return deleteLockedUsing(getMainKey().getFieldsArray());
}
@Override
public final void refresh() {
refreshUsing(getMainKey().getFieldsArray());