11. Transparent Persistence


The problem of updating deep object structures was briefly outlined in Structured objects chapter . Update Depth configuration setting gives a user a certain control over the object updating and allows to find a balance between performance and convenient object storage code. However, this balance is far from ideal:
- when update depth is zero, each piece of code should "know" how many levels of objects should be updated; this potentially creates lots of problems when the objects are refactored;
- when update depth is maximum performance can become very poor as many unchanged objects will be stored unnecessary.

The solution to this problem is to let db4o engine decide, which objects were modified and should be stored. This feature was introduced in db4o version 7.1 and was named Transparent Persistence. So how does it work?

1. Database should be configured to use TransparentPersistenceSupport.
2. Persistent classes available for Transparent Persistence must implement  Activatable interface. This interface provides a #bind()  method to bind an object to the object container's activator.
3. The object is bound to the ObjectContainer  when it is first stored or instantiated from the database.
4. When an object field is modified in the runtime,  #activate()  method is called to register the object to be stored with the next commit. The difference from Transparent Activation is in the activation purpose: ActivationPurpose.WRITE is used for TP.
5. When the transaction is committed or the database is closed, db4o traverses the list of modified Activatable objects and persists them.
Well, that's enough of theory, let's look at an example.


    11.1. Transparent Persistence Example

       
    We will use Car and SensorReadout classes from Deep Graphs chapter. These are persistent classes, so if we want to use Transparent Persistence, we should make them "visible" to Transparent Persistence by implementing Activatable  interface.

    package com.db4odoc.f1.chapter9;
    import java.util.*;
    import com.db4o.activation.*;
    import com.db4o.ta.*;
    public class Car implements Activatable {
        private String model;
        private SensorReadout history;
        private transient Activator _activator;
        public Car(String model) {
            this.model=model;
            this.history=null;
        }
        public String getModel() {
            activate(ActivationPurpose.READ);
            return model;
        }
        
        public SensorReadout getHistory() {
            activate(ActivationPurpose.READ);
            return history;
        }
        
        public void snapshot() {
            activate(ActivationPurpose.WRITE);
            appendToHistory(new TemperatureSensorReadout(
                    new Date(),this,"oil",pollOilTemperature()));
            appendToHistory(new TemperatureSensorReadout(
                    new Date(),this,"water",pollWaterTemperature()));
        }
        protected double pollOilTemperature() {
            return 0.1*countHistoryElements();
        }
        protected double pollWaterTemperature() {
            return 0.2*countHistoryElements();
        }
        public String toString() {
            activate(ActivationPurpose.READ);
            return model+"/"+countHistoryElements();
        }
        
        private int countHistoryElements() {
            activate(ActivationPurpose.READ);
            return (history==null ? 0 : history.countElements());
        }
        
        private void appendToHistory(SensorReadout readout) {
            activate(ActivationPurpose.WRITE);
            if(history==null) {
                history=readout;
            }
            else {
                history.append(readout);
            }
        }
        public void activate(ActivationPurpose purpose) {
            if(_activator != null) {
                _activator.activate(purpose);
            }
        }
        public void bind(Activator activator) {
            if (_activator == activator) {
                return;
            }
            if (activator != null && _activator != null) {
                throw new IllegalStateException();
            }
            _activator = activator;
        }
    }


    Note, that we've added an activator  field, bind  and activate  methods to implement Activatable  interface. In addition to that all methods that read or write object fields has got activate calls with a corresponding purpose.
    Similar modifications should be done to the SensorReadout class.
    Now we are ready to test how Transparent Persistence work. First we should configure the database to use TransparentPersistenceSupport before storing objects:        
    // storeCarAndSnapshots
    EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
    config.common().add(new TransparentPersistenceSupport());
    ObjectContainer db = Db4oEmbedded.openFile(config, DB4OFILENAME);
    Car car = new Car("Ferrari");
    for (int i = 0; i < 3; i++) {
        car.snapshot();
    }
    db.store(car);
    db.close();

    Ok, all the objects are stored.
    Now, let's retrieve all the stored objects and modify them:
    // modifySnapshotHistory
    EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
    config.common().add(new TransparentPersistenceSupport());
    ObjectContainer db = Db4oEmbedded.openFile(config, DB4OFILENAME);
    System.out.println("Read all sensors and modify the description:");
    ObjectSet result = db.queryByExample(Car.class);
    if (result.hasNext()) {
        Car car = (Car) result.next();
        SensorReadout readout = car.getHistory();
        while (readout != null) {
            System.out.println(readout);
            readout.setDescription("Modified: " + readout.getDescription());
            readout = readout.getNext();
        }
        db.commit();
    }
    db.close();

    You can see that we do not have to call #store any more - all the objects are stored when #commit is called.
    Let's test that the modifications actually reached the database:
    // readSnapshotHistory
    EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
    config.common().add(new TransparentPersistenceSupport());
    ObjectContainer db = Db4oEmbedded.openFile(config, DB4OFILENAME);
    System.out.println("Read all modified sensors:");
    ObjectSet result = db.queryByExample(Car.class);
    if (result.hasNext()) {
        Car car = (Car) result.next();
        SensorReadout readout = car.getHistory();
        while (readout != null) {
            System.out.println(readout);
            readout = readout.getNext();
        }
    }
    db.close();

    Yes, it is all as it should be. If you want to see the difference without Transparent Persistence, run the same examples withoutTransparentPersistenceSupport .


    11.2. Transparent Persistence Enhancement

    As we saw before enhancement tools can simplify the process for Transparent Activation. The same applies to Transparent Persistence. Actually Transparent Persistence enhancement implicitly provides TA for enhanced classes.
    For more information please refer to Enhancement chapter .


    11.3. Conclusion

    Transparent Persistence can considerably simplify the development process at the same time providing considerable performance benefits. For more information on Transparent Persistence please refer to our online reference  or your offline copy of the Reference documentation.   


    11.4. Full source


    package com.db4odoc.f1.chapter9;
    import java.io.*;
    import com.db4o.*;
    import com.db4o.config.*;
    import com.db4o.ta.*;
    import com.db4odoc.f1.*;
    public class TransparentPersistenceExample extends Util {
        final static String DB4OFILENAME = System.getProperty("user.home") + "/formula1.db4o";
        public static void main(String[] args) throws Exception {
            new File(DB4OFILENAME).delete();
            storeCarAndSnapshots();
            modifySnapshotHistory();
            readSnapshotHistory();
        }
        public static void storeCarAndSnapshots() {
            EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
            config.common().add(new TransparentPersistenceSupport());
            ObjectContainer db = Db4oEmbedded.openFile(config, DB4OFILENAME);
            Car car = new Car("Ferrari");
            for (int i = 0; i < 3; i++) {
                car.snapshot();
            }
            db.store(car);
            db.close();
        }
        public static void modifySnapshotHistory() {
            EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
            config.common().add(new TransparentPersistenceSupport());
            ObjectContainer db = Db4oEmbedded.openFile(config, DB4OFILENAME);
            System.out.println("Read all sensors and modify the description:");
            ObjectSet result = db.queryByExample(Car.class);
            if (result.hasNext()) {
                Car car = (Car) result.next();
                SensorReadout readout = car.getHistory();
                while (readout != null) {
                    System.out.println(readout);
                    readout.setDescription("Modified: " + readout.getDescription());
                    readout = readout.getNext();
                }
                db.commit();
            }
            db.close();
        }
        public static void readSnapshotHistory() {
            EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
            config.common().add(new TransparentPersistenceSupport());
            ObjectContainer db = Db4oEmbedded.openFile(config, DB4OFILENAME);
            System.out.println("Read all modified sensors:");
            ObjectSet result = db.queryByExample(Car.class);
            if (result.hasNext()) {
                Car car = (Car) result.next();
                SensorReadout readout = car.getHistory();
                while (readout != null) {
                    System.out.println(readout);
                    readout = readout.getNext();
                }
            }
            db.close();
        }
    }

    package com.db4odoc.f1.chapter9;
    import java.util.*;
    import com.db4o.activation.*;
    import com.db4o.ta.*;
    public class Car implements Activatable {
        private String model;
        private SensorReadout history;
        private transient Activator _activator;
        public Car(String model) {
            this.model=model;
            this.history=null;
        }
        public String getModel() {
            activate(ActivationPurpose.READ);
            return model;
        }
        
        public SensorReadout getHistory() {
            activate(ActivationPurpose.READ);
            return history;
        }
        
        public void snapshot() {
            activate(ActivationPurpose.WRITE);
            appendToHistory(new TemperatureSensorReadout(
                    new Date(),this,"oil",pollOilTemperature()));
            appendToHistory(new TemperatureSensorReadout(
                    new Date(),this,"water",pollWaterTemperature()));
        }
        protected double pollOilTemperature() {
            return 0.1*countHistoryElements();
        }
        protected double pollWaterTemperature() {
            return 0.2*countHistoryElements();
        }
        public String toString() {
            activate(ActivationPurpose.READ);
            return model+"/"+countHistoryElements();
        }
        
        private int countHistoryElements() {
            activate(ActivationPurpose.READ);
            return (history==null ? 0 : history.countElements());
        }
        
        private void appendToHistory(SensorReadout readout) {
            activate(ActivationPurpose.WRITE);
            if(history==null) {
                history=readout;
            }
            else {
                history.append(readout);
            }
        }
        public void activate(ActivationPurpose purpose) {
            if(_activator != null) {
                _activator.activate(purpose);
            }
        }
        public void bind(Activator activator) {
            if (_activator == activator) {
                return;
            }
            if (activator != null && _activator != null) {
                throw new IllegalStateException();
            }
            _activator = activator;
        }
    }

    package com.db4odoc.f1.chapter9;
    import java.util.*;
    import com.db4o.activation.*;
    import com.db4o.ta.*;
    public class SensorReadout implements Activatable {
        private Date time;
        private Car car;
        private String description;
        private SensorReadout next;
        private transient Activator _activator;
        protected SensorReadout(Date time,Car car,String description) {
            this.time=time;
            this.car=car;
            this.description=description;
            this.next=null;
        }
        public Car getCar() {
            activate(ActivationPurpose.READ);
            return car;
        }
        public Date getTime() {
            activate(ActivationPurpose.READ);
            return time;
        }
        public String getDescription() {
            activate(ActivationPurpose.READ);
            return description;
        }
        public void setDescription(String description) {
            activate(ActivationPurpose.WRITE);
            this.description = description;
        }
        public SensorReadout getNext() {
            activate(ActivationPurpose.READ);
            return next;
        }
        
        public void append(SensorReadout readout) {
            activate(ActivationPurpose.WRITE);
            if(next==null) {
                next=readout;
            }
            else {
                next.append(readout);
            }
        }
        
        public int countElements() {
            activate(ActivationPurpose.READ);
            return (next==null ? 1 : next.countElements()+1);
        }
        
        public String toString() {
            activate(ActivationPurpose.READ);
            return car+" : "+time+" : "+description;
        }
        public void activate(ActivationPurpose purpose) {
            if(_activator != null) {
                _activator.activate(purpose);
            }
        }
        public void bind(Activator activator) {
            if (_activator == activator) {
                return;
            }
            if (activator != null && _activator != null) {
                throw new IllegalStateException();
            }
            _activator = activator;
        }
    }

    package com.db4odoc.f1.chapter9;
    import java.util.*;
    import com.db4o.activation.*;

    public class TemperatureSensorReadout extends SensorReadout {
        private double temperature;
        
        public TemperatureSensorReadout(
                Date time,Car car,
                String description,double temperature) {
            super(time,car,description);
            this.temperature=temperature;
        }
        
        public double getTemperature() {
            activate(ActivationPurpose.READ);
            return temperature;
        }
        public String toString() {
            return super.toString()+" temp : "+temperature;
        }
    }







    www.db4o.com