Interface DataOperator<T extends BaseDataEntity<String>>
- Type Parameters:
T- Data type inherited from BaseDataEntity
- All Known Implementing Classes:
AbstractRelationalDataOperator,MysqlDataOperator,SimpleJsonDataOperator,SQLiteDataOperator
-
Nested Class Summary
Nested Classes -
Method Summary
Modifier and TypeMethodDescriptionvoiddel(WhereCondition... whereConditions) Delete data record by conditions.voidDelete data record by ID.booleanexist(WhereCondition... whereConditions) Check if the data record exists.booleanCheck if the data record exists.getAll()Get all data record.getAll(WhereCondition... whereConditions) Get all data record by conditions.Get data record by ID.getLike(String column, String value, DataOperator.LikeType likeType) Fuzzy QueryvoidInsert data record.default voidInsert multiple entities atomically: on every shipped backend either all of them are stored or, if one fails, none is.page(int page, int size, WhereCondition... whereConditions) Get data record by page.query()Returns a new fluent query builder for this data operator.default voidtransaction(Runnable action) Execute operations within a transaction (void variant), with the same guarantees astransaction(Callable)on every shipped backend, including the JSON backend's table lock at the first read of a transaction this method begins.default <R> Rtransaction(Callable<R> action) Execute operations within a transaction.voidUpdate one field of one record.voidUpdate data record by object.default voidUpdate multiple entities atomically: on every shipped backend either all of them are updated or, if one fails, none is.default intupdateCounted(T entity) Counted update:update(BaseDataEntity), returning how many rows it wrote, so the caller can tell that nothing was written (#558).default booleanupdateIf(T entity, WhereCondition... expected) Conditional update: writesentityover the stored row with the same id only if that row still matches every one ofexpected, and reports whether it did (#543).
-
Method Details
-
exist
Check if the data record exists.- Parameters:
object- Data record entity- Returns:
- Whether the record exists
-
exist
Check if the data record exists.- Parameters:
whereConditions- Conditions- Returns:
- Whether the record exists
-
getById
Get data record by ID.- Parameters:
id- Record ID- Returns:
- Data record
-
getAll
Get all data record.- Returns:
- Data record list
-
getAll
Get all data record by conditions.- Parameters:
whereConditions- Conditions- Returns:
- Data record list
-
getLike
Fuzzy Query- Parameters:
column- Column namevalue- Query valuelikeType- Like type- Returns:
- Data record list
-
page
Get data record by page.- Parameters:
page- Page numbersize- Page sizewhereConditions- Conditions- Returns:
- Data record list
-
insert
Insert data record.- Parameters:
obj- Data record
-
del
Delete data record by conditions.Since 6.3.0, a call with no conditions (a
nullor zero-lengthwhereConditions) is rejected with aDataAccessExceptioninstead of deleting every row of the table — this is a behavioral change with no migration period (COMPATIBILITY.md's security-fix channel); see 02-CONTEXT.md D-12.- Parameters:
whereConditions- Conditions
-
delById
Delete data record by ID.- Parameters:
id- Record ID
-
update
Update one field of one record.- Parameters:
column- Column namevalue- New valueid- Record ID
-
update
Update data record by object. It will not update the fields that the incoming entity does not have.- Parameters:
obj- Data record- Throws:
IllegalAccessException- Please referIllegalAccessException
-
updateCounted
Counted update:update(BaseDataEntity), returning how many rows it wrote, so the caller can tell that nothing was written (#558).An update by a non-null id that matches no row writes nothing on every backend: SQLite, MySQL and JSON each log one WARNING naming the table and the id, and return normally.
update(T)gives the caller no way to see that; this method returns0instead of1. Use it wherever "the row was gone" must be treated as a failure -- for example a write that credits an account that another server has just removed. For a write that should apply only while the stored row still has the values you read, useupdateIf(T, com.ultikits.ultitools.entities.WhereCondition...)instead.The framework's operators return the backend's own affected-row count. This default, which a third-party implementation inherits unless it overrides it, checks with
exist(WhereCondition...)whether a row with the entity's id exists, returns0without writing if it does not, and otherwise callsupdate(T)and returns1. It cannot see what thatupdatewrote, so one case remains in which it reports1although nothing was written: another writer deleting the row during that one call.- Parameters:
entity- the new state of the row, carrying the id of the row to write- Returns:
- the number of rows written:
1, or0when no row has that id - Throws:
DataAccessException- ifentity's id isnull, or the write fails- Since:
- 6.3.0
-
updateIf
Conditional update: writesentityover the stored row with the same id only if that row still matches every one ofexpected, and reports whether it did (#543).The check and the write are one step: on SQLite and MySQL a single
UPDATE ... WHERE id = ? AND <expected>whose affected-row count decides the result, so it holds across servers sharing one database; on the JSON backend the check and the write run under the operator's own lock (a JSON store is local to one server). The conditions mean exactly what they mean ingetAll(WhereCondition...)on the same backend, values are bound as parameters, and a condition whoseisEmpty()is true is ignored.Typical use is compare-and-set: read a row, compute the new state, and write it conditioned on the value that was read; if another writer changed the row in between, nothing is written and
falsecomes back, so the caller re-reads and decides again rather than writing on top. UltiEconomy's one-time wallet merge uses it this way, conditioned on the balance it read:
LikeAccount read = accounts.getById(id); double seen = read.getBalance(); read.setBalance(seen + amount); if (!accounts.updateIf(read, WhereCondition.builder().column("balance").value(seen).build())) { // someone else wrote first: re-read and decide again }update(BaseDataEntity), every mapped field ofentityis written, andonUpdate()fires onentitybefore the fields are read, whether or not the write then applies.A
nullexpected value meansIS NULLunder the defaultComparison.EQUALcomparison: the condition holds only while the stored column is unset -- on SQLite and MySQL<column> IS NULLinside the same single statement, on JSON an entry whose field is absent or JSON null. A compare-and-set against a column that was unset when it was read therefore misses, and keeps the value, when another writer filled the column in between. This meaning belongs toupdateIfalone:getAll(WhereCondition...),exist(WhereCondition...),del(WhereCondition...)andpage(int, int, WhereCondition...)treat anullcondition value as they always have. (6.3.0 snapshots published before this change refused anullexpected value; no release did.)- Parameters:
entity- the new state of the row, carrying the id of the row to writeexpected- the conditions the stored row must still meet- Returns:
trueif the row matched and was written;falseif no row with that id matched every condition, in which case nothing was written- Throws:
DataAccessException- ifentity's id isnull, a condition isnull, a condition names a column the entity does not map with@Column(also when its value isnull), or a condition's value isnullunder a comparison other thanEQUAL(onlyEQUALgivesnulla meaning; under any other the write could never apply) -- on every backend, so a misspelt column cannot turn a retry loop into an endless oneUnsupportedOperationException- if this implementation does not provide conditional writes -- the default, so a third-party implementation is never silently unconditional- Since:
- 6.3.0
-
query
Returns a new fluent query builder for this data operator.- Returns:
- a new Query builder
-
transaction
Execute operations within a transaction. All operations commit together or roll back on exception.Every backend this framework ships (SQLite, MySQL, JSON) runs the action in a transaction: it joins the transaction already active on the calling thread, if any, and any
Throwablethe action throws rolls it back and is rethrown. SQLite and MySQL use a database transaction. On JSON, the action's writes are staged until commit and are invisible to other threads until then; the commit applies them together, so other threads see all of them at once; a rollback discards them.On JSON, a transaction this method begins locks each table at its first read or write and holds the lock until it commits or rolls back, so a read-modify-write inside the action cannot lose an update, as in 6.2.5, where each JSON
transaction(...)callback ran alone per operator. A read inside the action can therefore wait for another transaction that holds the table: at most 5 seconds, then it fails with aDataAccessExceptionand the transaction rolls back. A call that joins an active transaction keeps that transaction's isolation: inside a@Transactionalmethod at the default isolation, JSON reads take no table lock.When the commit itself fails: on SQLite and MySQL, the exception does not tell whether the database stored the transaction (the outcome is unknown; see
TransactionManager.commit()), and nothing is retried, so code that must not apply a write twice re-reads the data, or usesupdateIf(T, com.ultikits.ultitools.entities.WhereCondition...), before trying again. On JSON a failed commit commits nothing. AnUnexpectedRollbackExceptionfrom the commit means the transaction was rolled back, none of its writes stored, on every backend.This default implementation, used only by a third-party operator that does not override it, runs the action directly without a transaction.
- Type Parameters:
R- the return type- Parameters:
action- the operations to execute- Returns:
- the result of the action
- Throws:
Exception- if the action fails
-
transaction
Execute operations within a transaction (void variant), with the same guarantees astransaction(Callable)on every shipped backend, including the JSON backend's table lock at the first read of a transaction this method begins. The same commit outcomes apply: seetransaction(Callable).This default implementation, used only by a third-party operator that does not override it, runs the action directly without a transaction.
- Parameters:
action- the operations to execute
-
insertAll
Insert multiple entities atomically: on every shipped backend either all of them are stored or, if one fails, none is. Uses JDBC batch for SQL backends; on JSON the batch is one transaction. Inside an active transaction the batch joins it.- Parameters:
entities- the entities to insert
-
updateAll
Update multiple entities atomically: on every shipped backend either all of them are updated or, if one fails, none is. Uses JDBC batch for SQL backends; on JSON the batch is one transaction. Inside an active transaction the batch joins it.This default implementation, used only by a third-party operator that does not override it, updates the entities one by one without a transaction.
- Parameters:
entities- the entities to update- Throws:
IllegalAccessException- if field access fails
-