[#1547] Support "optimistic locking" in UpdatableRecord.storeLocked()
- Added UpdatableRecord.deleteLocked()
This commit is contained in:
parent
b5e7029c60
commit
3f85fb95cd
@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
Loading…
Reference in New Issue
Block a user