Interface DataOperator<T extends BaseDataEntity<String>>

Type Parameters:
T - Data type inherited from BaseDataEntity
All Known Implementing Classes:
AbstractRelationalDataOperator, MysqlDataOperator, SimpleJsonDataOperator, SQLiteDataOperator

public interface DataOperator<T extends BaseDataEntity<String>>
Data operation interface.
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Interface
    Description
    static enum 
     
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    del(WhereCondition... whereConditions)
    Delete data record by conditions.
    void
    Delete data record by ID.
    boolean
    exist(WhereCondition... whereConditions)
    Check if the data record exists.
    boolean
    exist(T object)
    Check if the data record exists.
    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 Query
    void
    insert(T obj)
    Insert data record.
    default void
    insertAll(List<T> entities)
    Insert 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.
    default Query<T>
    Returns a new fluent query builder for this data operator.
    default void
    Execute operations within a transaction (void variant), with the same guarantees as transaction(Callable) on every shipped backend, including the JSON backend's table lock at the first read of a transaction this method begins.
    default <R> R
    transaction(Callable<R> action)
    Execute operations within a transaction.
    void
    update(String column, Object value, Object id)
    Update one field of one record.
    void
    update(T obj)
    Update data record by object.
    default void
    updateAll(List<T> entities)
    Update multiple entities atomically: on every shipped backend either all of them are updated or, if one fails, none is.
    default int
    updateCounted(T entity)
    Counted update: update(BaseDataEntity), returning how many rows it wrote, so the caller can tell that nothing was written (#558).
    default boolean
    updateIf(T entity, WhereCondition... expected)
    Conditional update: writes entity over the stored row with the same id only if that row still matches every one of expected, and reports whether it did (#543).
  • Method Details

    • exist

      boolean exist(T object)
      Check if the data record exists.
      Parameters:
      object - Data record entity
      Returns:
      Whether the record exists
    • exist

      boolean exist(WhereCondition... whereConditions)
      Check if the data record exists.
      Parameters:
      whereConditions - Conditions
      Returns:
      Whether the record exists
    • getById

      T getById(Object id)
      Get data record by ID.
      Parameters:
      id - Record ID
      Returns:
      Data record
    • getAll

      List<T> getAll()
      Get all data record.
      Returns:
      Data record list
    • getAll

      List<T> getAll(WhereCondition... whereConditions)
      Get all data record by conditions.
      Parameters:
      whereConditions - Conditions
      Returns:
      Data record list
    • getLike

      List<T> getLike(String column, String value, DataOperator.LikeType likeType)
      Fuzzy Query
      Parameters:
      column - Column name
      value - Query value
      likeType - Like type
      Returns:
      Data record list
    • page

      List<T> page(int page, int size, WhereCondition... whereConditions)
      Get data record by page.
      Parameters:
      page - Page number
      size - Page size
      whereConditions - Conditions
      Returns:
      Data record list
    • insert

      void insert(T obj)
      Insert data record.
      Parameters:
      obj - Data record
    • del

      void del(WhereCondition... whereConditions)
      Delete data record by conditions.

      Since 6.3.0, a call with no conditions (a null or zero-length whereConditions) is rejected with a DataAccessException instead 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

      void delById(Object id)
      Delete data record by ID.
      Parameters:
      id - Record ID
    • update

      void update(String column, Object value, Object id)
      Update one field of one record.
      Parameters:
      column - Column name
      value - New value
      id - Record ID
    • update

      void update(T obj) throws IllegalAccessException
      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

      default int updateCounted(T entity)
      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 returns 0 instead of 1. 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, use updateIf(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, returns 0 without writing if it does not, and otherwise calls update(T) and returns 1. It cannot see what that update wrote, so one case remains in which it reports 1 although 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, or 0 when no row has that id
      Throws:
      DataAccessException - if entity's id is null, or the write fails
      Since:
      6.3.0
    • updateIf

      default boolean updateIf(T entity, WhereCondition... expected)
      Conditional update: writes entity over the stored row with the same id only if that row still matches every one of expected, 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 in getAll(WhereCondition...) on the same backend, values are bound as parameters, and a condition whose isEmpty() 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 false comes 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:

      
       Account 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
       }
       
      Like update(BaseDataEntity), every mapped field of entity is written, and onUpdate() fires on entity before the fields are read, whether or not the write then applies.

      A null expected value means IS NULL under the default Comparison.EQUAL comparison: the condition holds only while the stored column is unset -- on SQLite and MySQL <column> IS NULL inside 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 to updateIf alone: getAll(WhereCondition...), exist(WhereCondition...), del(WhereCondition...) and page(int, int, WhereCondition...) treat a null condition value as they always have. (6.3.0 snapshots published before this change refused a null expected value; no release did.)

      Parameters:
      entity - the new state of the row, carrying the id of the row to write
      expected - the conditions the stored row must still meet
      Returns:
      true if the row matched and was written; false if no row with that id matched every condition, in which case nothing was written
      Throws:
      DataAccessException - if entity's id is null, a condition is null, a condition names a column the entity does not map with @Column (also when its value is null), or a condition's value is null under a comparison other than EQUAL (only EQUAL gives null a meaning; under any other the write could never apply) -- on every backend, so a misspelt column cannot turn a retry loop into an endless one
      UnsupportedOperationException - if this implementation does not provide conditional writes -- the default, so a third-party implementation is never silently unconditional
      Since:
      6.3.0
    • query

      default Query<T> query()
      Returns a new fluent query builder for this data operator.
      Returns:
      a new Query builder
    • transaction

      default <R> R transaction(Callable<R> action) throws Exception
      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 Throwable the 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 a DataAccessException and the transaction rolls back. A call that joins an active transaction keeps that transaction's isolation: inside a @Transactional method 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 uses updateIf(T, com.ultikits.ultitools.entities.WhereCondition...), before trying again. On JSON a failed commit commits nothing. An UnexpectedRollbackException from 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

      default void transaction(Runnable action)
      Execute operations within a transaction (void variant), with the same guarantees as transaction(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: see transaction(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

      default void insertAll(List<T> entities)
      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

      default void updateAll(List<T> entities) throws IllegalAccessException
      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