Skip to content

Read Only Objects

Jan Wiemer edited this page Dec 29, 2020 · 6 revisions

Read Only Objects

Why Read Only Objects

In the previous chapters it was explained that the methods providing access to the objects in the store first clone the object to the TX-View and then return the cloned object to the caller. The reason is to prevent the modification of the original object which would immediately would be seen by all other transactions and therefore violates isolation. As long as we access the objects we want to update by the key this is fine. However a common use-case is to read multiple objects from the store in a loop and only while traversing the loop decide to update some of the visited objects. In order to update only a few objects we have to clone all visited objects to the TX-View. Each clone operation needs some computation time and the cloned object also occupies some memory. Working with a huge amount of objects the effects quickly sum up. Therefore it is desirable to avoid the "unnecessary" clone operations and work with the original objects as long as the object is not modified. Note that this is only possible when the type of the internal store objects is the same as the external objects stored in the TX-View. Simply returning the original objects to the caller would be very risky since there is no control if the object is not accidentally modified. Therefore the concept of read only objects is introduced. The idea is to have a mechanism to switch the objects between a writable mode and a read only mode. The way this is done is implemented in a read only mode adapter (implementing the interface JacisStoreEntryReadOnlyModeAdapter). More details regarding the read only mode adapter can be found in the chapter Read Only Mode Adapter.

Read Only Mode Supporting Objects

The default implementation of the read only mode adapter delegates switching the read only mode to the object itself. For this purpose the objects have to implement the JacisReadonlyModeSupport interface (declaring a switchToReadOnlyMode and a switchToReadWriteMode method). Usually the objects implement this interface by extending the abstract AbstractReadOnlyModeSupportingObject class. Doing this we only have to guarantee that all methods modifying the state of the object first call the checkWritable method dprovided by the abstract implementation.

The following code snippet shows the Account class known from the Getting Started chapter adapted to support the read only mode:

  public class Account extends AbstractReadOnlyModeSupportingObject implements JacisCloneable<Account> {

    private final String name;
    private long balance;

    public Account(String name) {
      this.name = name;
    }

    @Override
    public Account clone() {
      return (Account) super.clone();
    }

    public Account deposit(long amount) {
      checkWritable();
      balance += amount;
      return this;
    }

    public Account withdraw(long amount) {
      checkWritable();
      balance -= amount;
      return this;
    }

    public String getName() {
      return name;
    }

    public long getBalance() {
      return balance;
    }

  }

Read Only Access Methods

The JACIS provides several methods to access the stored objects in read only mode. Basically all normal access methods also exist suffix …​ReadOnly returning the read only objects:

TV getReadOnly(K key);

Return the object for the passed key.

List<TV> getAllReadOnly();

Return all objects in the store as a List.

List<TV> getAllReadOnly(Predicate<TV> filter);

Return all objects matching the passed filter in the store as a List.

Stream<TV> streamReadOnly();

Return all objects in the store as a stream.

Stream<TV> streamReadOnly(Predicate<TV> filter);

Return all objects in the store matching the passed filter as a stream.

Examples Using Read Only Objects

The following examples using read only objects use the same example data as in the Accessing Objects chapter (10 Account instances with different balance).

    // Now we show some examples how to use read only objects:
    container.withLocalTx(() -> {

      // using stream methods (compute the total balance of all stored accounts):
      System.out.println("total balance=" + store.streamReadOnly().mapToLong(Account::getBalance).sum());

      // using a filter (count accounts with a balance > 500):
      System.out.println("#>500=" + store.streamReadOnly(acc -> acc.getBalance() > 500).count());

      // updating a read only object fails:
      try {
        store.update("account1", store.getReadOnly("account1").deposit(10));
      } catch (ReadOnlyException e) {
        System.out.println("caught expected Exception: " + e);
      }

      // looping through the elements (adding writing an output)
      store.streamReadOnly().forEach(acc -> {
        if (acc.getBalance() < 0) {
          acc = store.get(acc.getName());
          acc.deposit(42);
        }
      });
    });

Next Chapter: Atomic Operations