Class AbstractEntityConnection

java.lang.Object
is.codion.framework.db.AbstractEntityConnection
All Implemented Interfaces:
EntityConnection, AutoCloseable

public abstract class AbstractEntityConnection extends Object implements EntityConnection

An abstract self-managing EntityConnection, one which establishes the underlying connection on demand, validates it before each operation and re-establishes it when it has gone bad. The same instance therefore serves for the lifetime of a client, there being no need to fetch a connection per operation.

It is a connection going bad that is healed: close() is terminal, subsequent operations throw IllegalStateException, an explicit close being a decision, not a mishap.

The transaction state lives on this instance rather than being asked of the underlying connection, which, once it has gone bad, can no longer answer: a connection with an open transaction is never validated nor replaced, so that an operation on one which has gone bad fails loudly instead of the transaction being silently discarded, and transactionOpen() answers without a round trip. See commitTransaction() and rollbackTransaction() for how ending a transaction on a connection which has gone bad behaves.

Subclasses supply the transport by implementing connect(), and reach the current underlying connection via delegate() should they need to serve methods of their own.

See Also:
  • Constructor Details

  • Method Details

    • entities

      public final Entities entities()
      Specified by:
      entities in interface EntityConnection
      Returns:
      the underlying domain entities
    • user

      public final User user()
      Specified by:
      user in interface EntityConnection
      Returns:
      the user being used by this connection
    • clientId

      public final UUID clientId()
      Specified by:
      clientId in interface EntityConnection
      Returns:
      the client id for this connection
    • clientVersion

      public final Optional<Version> clientVersion()
      Description copied from interface: EntityConnection
      Returns the version of the client this connection belongs to, as reported to the server.

      Empty unless this connection manages itself, see EntityConnection.builder().

      Specified by:
      clientVersion in interface EntityConnection
      Returns:
      the client version, an empty Optional if none was specified
    • connected

      public final boolean connected()
      Reports the state of the underlying connection without healing it, this being the question a connection is asked, not a request to establish one.
      Specified by:
      connected in interface EntityConnection
      Returns:
      true if the underlying connection has been established and is valid
    • close

      public final void close()

      Closes this connection, rolling back any open transaction with it. Closing is terminal: subsequent operations throw IllegalStateException. It is a connection going bad that is healed by re-establishment, not one explicitly closed.

      Closing an already closed connection has no effect.

      Specified by:
      close in interface AutoCloseable
      Specified by:
      close in interface EntityConnection
    • transactionOpen

      public final boolean transactionOpen()
      Answered from the transaction state maintained by this instance, without consulting, or establishing, the underlying connection.
      Specified by:
      transactionOpen in interface EntityConnection
      Returns:
      true if a transaction is open
    • startTransaction

      public final void startTransaction()
      Description copied from interface: EntityConnection
      Starts a transaction on this connection.

      NOTE: A transaction should ALWAYS be used in conjunction with a try/catch block,
      in order for the transaction to be properly ended in case of an exception.
      A transaction should always be started OUTSIDE the try/catch block.

       EntityConnection connection = connection();
      
       connection.startTransaction(); // Very important, should NOT be inside the try block
       try {
           connection.insert(entity);
      
           connection.commitTransaction();
       }
       catch (DatabaseException e) {
           connection.rollbackTransaction();
           throw e;
       }
       catch (Exception e) {          // Very important to catch Exception
           connection.rollbackTransaction();
           throw new RuntimeException(e);
       }
      
      Specified by:
      startTransaction in interface EntityConnection
      See Also:
    • rollbackTransaction

      public final void rollbackTransaction()

      Rolls back the open transaction. Should the rollback call itself fail, the underlying connection is discarded and the failure logged rather than propagated - the disconnect rolls the transaction back, which is the outcome the caller asked for, and the exception which caused the caller to roll back remains the one being reported. The next operation establishes a fresh connection.

      Specified by:
      rollbackTransaction in interface EntityConnection
      See Also:
    • commitTransaction

      public final void commitTransaction()

      Commits the open transaction. Should the commit call fail on a connection which is no longer valid, the connection is discarded - the transaction died with it - and the next operation establishes a fresh one. A failed commit on a valid connection leaves the transaction open, the caller decides whether to retry or roll back.

      Specified by:
      commitTransaction in interface EntityConnection
      See Also:
    • cacheQueries

      public final EntityConnection.QueryCache cacheQueries()
      Description copied from interface: EntityConnection
      Enables query result caching on this connection until the returned EntityConnection.QueryCache is closed.

      While active, entity select results are cached and identical selects (by EntityConnection.Select equality) return the cached result. Intended for short-lived, read-only scopes, such as application or model initialization, where the same lookup entities would otherwise be selected repeatedly:

       try (QueryCache cache = connection.cacheQueries()) {
         // initialize application models
       }
      

      Contract:

      • Cached results are shared instances, the same List is returned for every cache hit. The list is unmodifiable and its entities are immutable, along with their foreign key references, so that no caller can modify what the next one receives. Use Entity.copy().mutable() to obtain a modifiable copy.
      • The cache is not invalidated by inserts, updates, deletes or transaction rollback on this connection, a select performed inside a transaction and cached may represent uncommitted or rolled-back data.
      • EntityConnection.Select.forUpdate() selects always bypass the cache and return mutable entities.
      • Scopes do not nest, calling this method while a cache is active throws IllegalStateException.
      Specified by:
      cacheQueries in interface EntityConnection
      Returns:
      a new active EntityConnection.QueryCache
    • execute

      public final <C extends EntityConnection, P, R> @Nullable R execute(FunctionType<C,P,R> functionType)
      Description copied from interface: EntityConnection
      Executes the function with the given type with no parameter
      Specified by:
      execute in interface EntityConnection
      Type Parameters:
      C - the connection type
      P - the parameter type
      R - the return value type
      Parameters:
      functionType - the function type
      Returns:
      the function return value
    • execute

      public final <C extends EntityConnection, P, R> @Nullable R execute(FunctionType<C,P,R> functionType, @Nullable P parameter)
      Description copied from interface: EntityConnection
      Executes the function with the given type
      Specified by:
      execute in interface EntityConnection
      Type Parameters:
      C - the connection type
      P - the parameter type
      R - the return value type
      Parameters:
      functionType - the function type
      parameter - the function parameter
      Returns:
      the function return value
    • execute

      public final <C extends EntityConnection, P> void execute(ProcedureType<C,P> procedureType)
      Description copied from interface: EntityConnection
      Executes the procedure with the given type with no parameter
      Specified by:
      execute in interface EntityConnection
      Type Parameters:
      C - the connection type
      P - the procedure parameter type
      Parameters:
      procedureType - the procedure type
    • execute

      public final <C extends EntityConnection, P> void execute(ProcedureType<C,P> procedureType, @Nullable P parameter)
      Description copied from interface: EntityConnection
      Executes the procedure with the given type
      Specified by:
      execute in interface EntityConnection
      Type Parameters:
      C - the connection type
      P - the parameter type
      Parameters:
      procedureType - the procedure type
      parameter - the procedure parameter
    • insert

      public final Entity.Key insert(Entity entity)
      Description copied from interface: EntityConnection
      Inserts the given entity, returning the primary key. Performs a commit unless a transaction is open.
       Entities entities = connection.entities();
      
       Entity artist = entities.entity(Artist.TYPE)
           .with(Artist.NAME, "The Beatles")
           .build();
      
       Entity.Key artistKey = connection.insert(artist);
      
      Specified by:
      insert in interface EntityConnection
      Parameters:
      entity - the entity to insert
      Returns:
      the primary key of the inserted entity
    • insertSelect

      public final Entity insertSelect(Entity entity)
      Description copied from interface: EntityConnection
      Inserts the given entity, returning the inserted entity. Performs a commit unless a transaction is open.

      The returned entity includes any lazy-loaded attributes (defined with ColumnDefinition.Builder.selected(boolean)) that were contained in the entity being inserted.

       Entity album = entities.entity(Album.TYPE)
           .with(Album.ARTIST_FK, artist)
           .with(Album.TITLE, "Abbey Road")
           .build();
      
       // Insert and get the entity with generated ID
       album = connection.insertSelect(album);
       Long generatedId = album.get(Album.ID);
      
      Specified by:
      insertSelect in interface EntityConnection
      Parameters:
      entity - the entity to insert
      Returns:
      the inserted entity
    • insert

      public final Collection<Entity.Key> insert(Collection<Entity> entities)
      Description copied from interface: EntityConnection
      Inserts the given entities, returning the primary keys in the same order as they were received. Performs a commit unless a transaction is open.
      Specified by:
      insert in interface EntityConnection
      Parameters:
      entities - the entities to insert
      Returns:
      the primary keys of the inserted entities, in the same order as they were received
    • insertSelect

      public final Collection<Entity> insertSelect(Collection<Entity> entities)
      Description copied from interface: EntityConnection
      Inserts the given entities, returning the inserted entities. Performs a commit unless a transaction is open.

      The returned entities include any lazy-loaded attributes (defined with ColumnDefinition.Builder.selected(boolean)) that were contained in the entities being inserted.

      Note: When inserting multiple entities, if a lazy attribute is contained in any of the entities, it will be included in the select for all of them. This means that if one entity has a lazy attribute with a value, that attribute will be loaded for all entities in the batch, even those where it wasn't contained.

      Specified by:
      insertSelect in interface EntityConnection
      Parameters:
      entities - the entities to insert
      Returns:
      the inserted entities, in no particular order
    • update

      public final void update(Entity entity)
      Description copied from interface: EntityConnection
      Updates the given entity based on its attribute values. Throws an exception if the given entity is unmodified. Performs a commit unless a transaction is open.
      Specified by:
      update in interface EntityConnection
      Parameters:
      entity - the entity to update
    • updateSelect

      public final Entity updateSelect(Entity entity)
      Description copied from interface: EntityConnection
      Updates the given entity based on its attribute values. Returns the updated entity. Throws an exception if the given entity is unmodified. Performs a commit unless a transaction is open.

      The returned entity includes any lazy-loaded attributes (defined with ColumnDefinition.Builder.selected(boolean)) that were contained in the entity being updated, preventing lazy-loaded values from being lost during updates.

      Specified by:
      updateSelect in interface EntityConnection
      Parameters:
      entity - the entity to update
      Returns:
      the updated entity
    • update

      public final void update(Collection<Entity> entities)
      Description copied from interface: EntityConnection
      Updates the given entities based on their attribute values. Throws an exception if any of the given entities is unmodified. Performs a commit unless a transaction is open.
      Specified by:
      update in interface EntityConnection
      Parameters:
      entities - the entities to update
    • updateSelect

      public final Collection<Entity> updateSelect(Collection<Entity> entities)
      Description copied from interface: EntityConnection
      Updates the given entities based on their attribute values. Returns the updated entities, in no particular order. Throws an exception if any of the given entities is unmodified. Performs a commit unless a transaction is open.

      The returned entities include any lazy-loaded attributes (defined with ColumnDefinition.Builder.selected(boolean)) that were contained in the entities being updated, preventing lazy-loaded values from being lost during updates.

      Note: When updating multiple entities, if a lazy attribute is contained in any of the entities, it will be included in the select for all of them. This means that if one entity has a lazy attribute loaded, that attribute will be loaded for all entities in the batch, even those where it wasn't originally loaded.

      Specified by:
      updateSelect in interface EntityConnection
      Parameters:
      entities - the entities to update
      Returns:
      the updated entities, in no particular order
    • update

      public final int update(EntityConnection.Update update)
      Description copied from interface: EntityConnection
      Performs an update based on the given update, updating the columns found in the EntityConnection.Update.values() map, using the associated value.
       // Update all customers without email
       int updatedCount = connection.update(
           Update.where(Customer.EMAIL.isNull())
               .set(Customer.EMAIL, "noemail@example.com")
               .set(Customer.SUPPORTREP_ID, supportRepId));
      
       // Bulk price increase
       int tracksUpdated = connection.update(
           Update.where(Track.GENRE_FK.equalTo(genre))
               .set(Track.UNITPRICE, newPrice));
      
      Specified by:
      update in interface EntityConnection
      Parameters:
      update - the update to perform
      Returns:
      the number of affected rows
    • delete

      public final void delete(Entity.Key key)
      Description copied from interface: EntityConnection
      Deletes the entity with the given primary key. Performs a commit unless a transaction is open.
      Specified by:
      delete in interface EntityConnection
      Parameters:
      key - the primary key of the entity to delete
    • delete

      public final void delete(Collection<Entity.Key> keys)
      Description copied from interface: EntityConnection
      Deletes the entities with the given primary keys. This method respects the iteration order of the given collection by first deleting all entities of the first entityType encountered, then all entities of the next entityType encountered and so on. This allows the deletion of multiple entities forming a master detail hierarchy, by having the detail entities appear before their master entities in the collection. Performs a commit unless a transaction is open.
      Specified by:
      delete in interface EntityConnection
      Parameters:
      keys - the primary keys of the entities to delete
    • delete

      public final int delete(Condition condition)
      Description copied from interface: EntityConnection
      Deletes the entities specified by the given condition. Performs a commit unless a transaction is open.
      Specified by:
      delete in interface EntityConnection
      Parameters:
      condition - the condition specifying the entities to delete
      Returns:
      the number of deleted rows
    • select

      public final <T> List<T> select(Column<T> column)
      Description copied from interface: EntityConnection
      Selects ordered and distinct non-null values of the given column.
      Specified by:
      select in interface EntityConnection
      Type Parameters:
      T - the value type
      Parameters:
      column - column
      Returns:
      the values of the given column
    • select

      public final <T> List<T> select(Column<T> column, Condition condition)
      Description copied from interface: EntityConnection
      Selects distinct non-null values of the given column. The result is ordered by the selected column.
      Specified by:
      select in interface EntityConnection
      Type Parameters:
      T - the value type
      Parameters:
      column - column
      condition - the condition
      Returns:
      the values of the given column
    • select

      public final <T> List<T> select(Column<T> column, EntityConnection.Select select)
      Description copied from interface: EntityConnection
      Selects distinct non-null values of the given column. If the select provides no order by clause the result is ordered by the selected column.
      Specified by:
      select in interface EntityConnection
      Type Parameters:
      T - the value type
      Parameters:
      column - column
      select - the select to perform
      Returns:
      the values of the given column
    • select

      public final Entity select(Entity.Key key)
      Description copied from interface: EntityConnection
      Selects an entity by key
      Specified by:
      select in interface EntityConnection
      Parameters:
      key - the key of the entity to select
      Returns:
      an entity having the key key
    • selectSingle

      public final Entity selectSingle(Condition condition)
      Description copied from interface: EntityConnection
      Selects a single entity based on the specified condition
      Specified by:
      selectSingle in interface EntityConnection
      Parameters:
      condition - the condition specifying the entity to select
      Returns:
      the entity based on the given condition
    • selectSingle

      public final Entity selectSingle(EntityConnection.Select select)
      Description copied from interface: EntityConnection
      Selects a single entity based on the specified select
      Specified by:
      selectSingle in interface EntityConnection
      Parameters:
      select - the select to perform
      Returns:
      the entity based on the given select
    • select

      public final Collection<Entity> select(Collection<Entity.Key> keys)
      Description copied from interface: EntityConnection
      Selects entities based on the given keys
      Specified by:
      select in interface EntityConnection
      Parameters:
      keys - the keys used in the condition
      Returns:
      entities based on keys
    • select

      public final List<Entity> select(Condition condition)
      Description copied from interface: EntityConnection
      Selects entities based on the given condition
       // Select all jazz albums
       Entity jazz = connection.selectSingle(Genre.NAME.equalTo("Jazz"));
       List<Entity> jazzTracks = connection.select(Track.GENRE_FK.equalTo(jazz));
      
       // Select with composite condition
       List<Entity> longExpensiveTracks = connection.select(and(
           Track.UNITPRICE.greaterThan(0.99),
           Track.MILLISECONDS.greaterThan(300_000)));
      
      Specified by:
      select in interface EntityConnection
      Parameters:
      condition - the condition specifying which entities to select
      Returns:
      entities based on the given condition
    • select

      public final List<Entity> select(EntityConnection.Select select)
      Description copied from interface: EntityConnection
      Selects entities based on the given select
       // Select with ordering and limit
       List<Entity> recentInvoices = connection.select(
           Select.where(Invoice.CUSTOMER_FK.equalTo(customer))
               .orderBy(OrderBy.descending(Invoice.DATE))
               .limit(10));
      
       // Select specific attributes only
       List<Entity> trackInfo = connection.select(
           Select.where(Track.ALBUM_FK.equalTo(album))
               .attributes(Track.NAME, Track.MILLISECONDS));
      
       // Control foreign key fetching depth
       List<Entity> tracks = connection.select(
           Select.where(Track.GENRE_FK.equalTo(genre))
               .referenceDepth(0));  // Don't fetch foreign keys
      
      Specified by:
      select in interface EntityConnection
      Parameters:
      select - the select to perform
      Returns:
      entities based on the given select
    • dependencies

      public final Map<EntityType,Collection<Entity>> dependencies(Collection<Entity> entities)
      Description copied from interface: EntityConnection
      Selects the entities that depend on the given entities via (non-soft) foreign keys, mapped to corresponding entityTypes
      Specified by:
      dependencies in interface EntityConnection
      Parameters:
      entities - the entities for which to retrieve dependencies, must be of same type
      Returns:
      the entities that depend on entities
      See Also:
    • count

      public final int count(EntityConnection.Count count)
      Description copied from interface: EntityConnection
      Counts the number of rows returned based on the given count conditions
      Specified by:
      count in interface EntityConnection
      Parameters:
      count - the count conditions
      Returns:
      the number of rows fitting the given count conditions
    • report

      public final <P, R> R report(ReportType<P,R> reportType, @Nullable P parameter)
      Description copied from interface: EntityConnection
      Takes a ReportType object using a JDBC datasource and returns an initialized report result object
      Specified by:
      report in interface EntityConnection
      Type Parameters:
      P - the report parameters type
      R - the report result type
      Parameters:
      reportType - the report to fill
      parameter - the report parameter, if any
      Returns:
      the filled result object
      See Also:
    • iterator

      public final EntityResultIterator iterator(Condition condition)
      Description copied from interface: EntityConnection
      Returns a result set iterator based on the given query condition.

      Remote Connection Performance: When using remote connections, each call to Iterator.hasNext() and Iterator.next() involves a network round-trip. For large result sets, consider using EntityConnection.select(Condition) instead to load entities in a single batch.

      Remote Connection Resource Management: Iterators over remote connections that remain idle for longer than the configured timeout (codion.db.remote.iteratorTimeout, default 5 minutes) are automatically closed server-side.

      Local Connection Sharing: A local iterator drives a live result set outside the connection monitor, so do not perform other operations on the same connection while iterating, and on databases whose cursors do not survive a commit (such as PostgreSQL) wrap the iteration in a transaction to keep the cursor open.

      Always use try-with-resources to ensure proper cleanup:

       try (EntityResultIterator iterator = connection.iterator(condition)) {
         while (iterator.hasNext()) {
           Entity entity = iterator.next();
           // process entity
         }
       }
      
      Specified by:
      iterator in interface EntityConnection
      Parameters:
      condition - the query condition
      Returns:
      an iterator for the given query condition
      See Also:
    • iterator

      public final EntityResultIterator iterator(EntityConnection.Select select)
      Description copied from interface: EntityConnection
      Returns a result set iterator based on the given select.

      Remote Connection Performance: When using remote connections, each call to Iterator.hasNext() and Iterator.next() involves a network round-trip. For large result sets, consider using EntityConnection.select(Select) instead to load entities in a single batch.

      Remote Connection Resource Management: Iterators over remote connections that remain idle for longer than the configured timeout (codion.db.remote.iteratorTimeout, default 5 minutes) are automatically closed server-side.

      Local Connection Sharing: A local iterator drives a live result set outside the connection monitor, so do not perform other operations on the same connection while iterating, and on databases whose cursors do not survive a commit (such as PostgreSQL) wrap the iteration in a transaction to keep the cursor open.

      Always use try-with-resources to ensure proper cleanup:

       try (EntityResultIterator iterator = connection.iterator(select)) {
         while (iterator.hasNext()) {
           Entity entity = iterator.next();
           // process entity
         }
       }
      
      Specified by:
      iterator in interface EntityConnection
      Parameters:
      select - the query select
      Returns:
      an iterator for the given query select
      See Also:
    • toString

      public final String toString()
      Overrides:
      toString in class Object
    • domainType

      protected final DomainType domainType()
      Returns:
      the domain type this connection is based on
      See Also:
    • clientType

      protected final String clientType()
      Returns:
      the client type identifying this connection to the server
      See Also:
    • delegate

      protected final EntityConnection delegate()

      Returns the underlying connection, establishing it if this is the first use and re-establishing it if it has gone bad. Called before each operation, hence EntityConnection.VALIDITY_CHECK_INTERVAL.

      Note that a connection with an open transaction is returned as is, without validation, so that an operation on one which has gone bad fails rather than the transaction being discarded without the caller ever hearing about it, see startTransaction().

      Returns:
      the underlying connection
      Throws:
      IllegalStateException - in case this connection has been closed
    • connect

      protected abstract EntityConnection connect()
      Returns:
      a new underlying connection
    • close

      protected void close(EntityConnection connection)

      Closes the given connection, called when this connection is closed and, best effort, when a bad connection is being replaced or discarded. Transports override this to release any related resources, deregistering with a server for example.

      Parameters:
      connection - the connection to close
    • established

      protected final Optional<EntityConnection> established()

      Returns the underlying connection should one be established, without validating it, establishing one or throwing in case this connection has been closed. For subclasses needing to configure the current underlying connection, should one exist - connect() being where a new one gets configured.

      Returns:
      the established underlying connection, or an empty Optional if none exists