Dark Planet Development Platform

Get Dark Planet Development Platform at SourceForge.net. Fast, secure and Free Open Source software downloads

Persistence API

The Persistence API provides functions for storing and retrieving various types of data model and generic data. There are two types of functions in the persistence API:

  1. Data model storage methods, which cover storing and retrieving instances of the data model; and,
  2. Hibernate factory and session methods, which cover obtaining Hibernate sessions for accessing various types of data, as well as factory methods to simplify constructing records for storage via Hibernate.

API Overview

The main gateway interface for the persistence API is org.dps.core.persist.IEvePersistenceFactory. As described above, you can think of this interface as divided into two parts. The first contains methods for storing and retrieving instances of the data model:

Return value Method Function
Capsuleer loadCharacter(int keyID) Load the persisted Capsuleer instance corresponding to the given key ID.
Corporation loadCorporation(int keyID) Load the persisted Corporation instance corresponding to the given corporation ID.
void saveCharacter(Capsuleer c) Persist the given Capsuleer instance.
void saveCorporation(Corporation c) Persist the given Corporation instance.
Collection<Integer> getCharacterKeys() Return a list of key IDs corresponding to characters stored by this persistence factory.
Collection<Integer> getCorporationKeys() Return a list of key IDs corresponding to corporations stored by this persistence factory.

The "save" and "load" methods provide the basic mechanism for storing and later retrieving data model information. Data model objects are designed to be persisted via Java serialization. However, persistence API implementations are free to store data model instances however they like. The "get" methods allow for discovering all available persisted data model information.

The second group of methods in the persistence API provide methods for working with Hibernate stored data, of which we only show a subset here:

Return value Method Function
SessionFactory getGenericDataSessionFactory() Retrieve a Hibernate Session factory which may be used to access generic information tables, for example external market data.
SessionFactory getKeySessionFactory(int keyID) Retrieve a Hibernate Session factory which may be used to access tables associated with the specified key ID.
IWalletJournalRecord getNewJournalRecord(...) Instantiate a new WalletJournalRecord ready for population and eventual storage in a Hibernate table.
... other record factory methods ...

Working with Hibernate stored data requires a Session which is normally provided by a SessionFactory. In DPS there are two distinct groups of Hibernate tables:

The two "get...SessionFactory" methods provide a means to get an appropriate factory for working with each type of data. We distinguish the two types of data to allow more flexibility in storage.

A "record" represents a data object stored (or intended to be stored) in a Hibernate table. Most of the methods in the persistence API are factory methods which allow the construction of new records. The arguments to a factory method are always the key columns of the table in which the record will be stored. For example, the full specification of the "getNewJournalRecord" methods is:

     IWalletJournalRecord getNewJournalRecord(int keyID, long charOrCorpID, int accountKey, long refID);
This means that the key columns of the "wallet journal" table are:

Record key columns are chosen to allow as much flexibility as possible in the persistence API implementation. For example, since "keyID" is a key column, you can choose to store all records in a single common table shared across all characters or corporations. Or, for performance reasons, you may wish to store character and corporation data in completely separate tables.

Example: Using the Store and Retrieve Methods

In this example, we'll create a simple Capsuleer, store it, then retrieve it. If you read the Data Model API description you know that we need an instance of IEveDataModel in order to create a new Capsuleer:

     // Create a Capsuleer
     int keyID = ... you keyID ...;
     IEveDataModel dataModel = ... reference to your data model ...;
     IModelFactory factory = dataModel.getModelFactory();
     Capsuleer myCharacter = factory.makeCharacter(keyID, vCode, characterID);

     // Save our capsuleer
     IEvePersistenceFactory pFactory = ... reference to your persistence factory ...;
     pFactory.saveCharacter(myCharacter);

     // Verify that we can find it again
     Collection charKeys = pFactory.getCharacterKeys();
     assert charKeys.contains(keyID);
    
     // Reload
     Capsuleer loadedChar = pFactory.loadCharacter(keyID);
The store and load methods throw predictable exceptions for failures. For example, load and store throw IOException if an error occurs while reading or storing data. Load methods will throw NoSuchCharacterException or NoSuchCorporationException if a request is made for an unknown character or corporation. Finally, load methods may throw a ClassNotFoundException while attempting to restore character or corporation data. This can happen when attempting to restore persisted data from an incompatible version of DPS.

Example: Storing and Retrieving Records in Hibernate Tables

We've already covered storing and retrieving Hibernate based data in other API documentation including the Data Model API and the Marke Data API descriptions. In this section, we'll just review the basic low-level methods and how they should be used. In most cases, you'll use one of the API specific high-level methods for populating data.

There are five steps to inserting data into a Hibernate table:

  1. Create an instance of the class you wish to insert. Be sure to populate at least the key column fields.
  2. Obtain a Session from a Hibernate SessionFactory.
  3. Begin a transaction using your Session.
  4. Use the "merge" or "update" method to save your class instance.
  5. Commit the transaction or rollback.
The DPS API provides interfaces for the data stored in Hibernate tables, which means that unless you break abstraction boundaries, you won't have any choice but to use one of the factory methods for instantiating Hibernate-backed data. The API was puposely designed this way so that users with little familiarity with Hibernate will still be able to make good use of DPS. However, the implementation behind the factory methods is not very complicated. Here's what the DPS reference implementation for the persistence API looks like for constructing a new journal record:

	@Override
	public IWalletJournalRecord getNewJournalRecord(int keyID, long entityID,
			int accountKey, long refID) {
		WalletJournalRecordImpl newEntry = new WalletJournalRecordImpl();
		newEntry.setKeyID(keyID);
		newEntry.setEntityID(entityID);
		newEntry.setAccountKey(accountKey);
		newEntry.setRefID(refID);

		return newEntry;
	}

The class WalletJournalRecordImpl implements IWalletJournalRecord. The code here just ensures that the fields representing the key columns are always populated. It's up to the caller to populate everything else.

Beyond object construction, you can use the Hibernate API methods directly if needed to insert new records. Here's the code for the five step process we described above assuming we want to insert a new wallet journal record (using the persistence API to obtain a Session):

    // Create record to insert
    IEvePersistenceFactory persistFactory = ...get persist reference...;
    IWalletJournalRecord newRecord = persistFactory.getNewJournalRecord(keyID, entityID, accountKey, refID);
    ... populate other journal record fields ...;

    // Obtain session
    Session txn = persistFactory.getKeySessionFactory(keyID).openSession();

    try {
    
         // Start transaction
         txn.begin();

         // Store record
         txn.merge(newRecord);

         // Commit
         txn.commit();

    } catch (HibernateException e) {
         // Commit failed, rollback
         txn.rollback();
    }

In terms of storing your new record, there are at least choices:

  1. You can save the record, which can be used for a unique new record, but will fail if the record already exists.
  2. You can update the record. You would use this method if the object you are storing was already retrieved from the database and you are simply updating some fields.
  3. You can merge the record, which acts like a save if the record is new, and acts like an update if the record already exists.
We chose to use "merge" in our example above because this is often the safest choice if you're not sure about the state of the table before inserting your record. A "save" is more efficient if you know for certain that the record is new.

Records are retrieved from a Hibernate table in five steps:

  1. Obtain a Session from a Hibernate SessionFactory.
  2. Begin a transction using your Session.
  3. Construct a query to obtain the object you want from the desired table.
  4. Execute your query and save the result(s).
  5. Commit the transaction or rollback.
For illustration, let's assume we want to obtain the journal record we just inserted above (and for some reason we no longer have a reference to the object). Here's the code to do that:
    // Obtain session
    IEvePersistenceFactory persistFactory = ...get persist reference...;
    Session txn = persistFactory.getKeySessionFactory(keyID).openSession();

    // Assume we know the refID of the record we want
    long refID = ... refID we want ...;

    // This variable will store the results
    List results;

    try {
    
         // Start transaction
         txn.begin();

         // Construct a query
         Query select = txn.createQuery("from org.dps.core.persist.IWalletJournalRecord where refID = :refID");
         select.setLong("refID", refID);

         // Execute the query
         results = select.list();

         // Commit
         txn.commit();

    } catch (HibernateException e) {
         // Commit failed, rollback
         txn.rollback();
    }
If the transaction commits, then "results" will hold all the journal records which matched our query (if any). The syntax for queries is any legal query in the Hibernate Query Language (HQL). Note that the "name" of the table against which the query is made is the fully qualified class name of the record interface type.

While the DPS API provides many helper functions for inserting records, there are very few factory-type methods for performing queries. This is because the type of query needed will vary widely among different applications. Instead, DPS provides a few simple methods to make it easier to manage Session objects (see the discussion of the HibernateCachedData type here). It is largely up to user to construct their own queries.

For more details on query construction, see the Hibernate documentation here.