Item handling

Contents

  1. Diagram of classes and methods
  2. Creating a new item
  3. Loading an existing item
  4. Deleting an existing item
  5. Detach and reattach an item
  6. Committing the transaction
  7. Rollback the transaction
  8. Disconnecting and reconnecting the database
  9. Cleaning up

See also

Last updated: $Date: 2009-04-06 14:52:39 +0200 (må, 06 apr 2009) $

1. Diagram of classes and methods

The DbControl is the class with the main responsibility for item handling. To it's help it has the HibernateUtil which collects most methods interfacing with Hibernate. You create a new DbControl object by calling the SessionControl.newDbControl() method. A DbControl object is not thread-safe and should not be used by multiple threads at the same time.

2. Creating a new item

The creation of a new item is initialised by a client application calling the static AnyItem.getNew() method on the item class. The call then proceeds as follows (not actual code):

1. AnyItem.getNew(DbControl, optional parameters)
2. |  DbControl.newItem(itemClass, dataClass)
3. |  |  data = new AnyData()
4. |  |  item = new AnyItem(data)
5. |  |  item.setDbControl(this)
6. |  optionally initialise other properties, ie. call set methods
7. |  return item to client application

Now the client application has a detached object. It will not be saved to the database automatically. For this to happen the client application must call the DbControl.saveItem() method. The call then proceeds as follows:

1. DbControl.saveItem(item)
2. |  check that the item is not already saved (ie, id == 0)
3. |  item.setDbControl(this)
4. |  item.initPermissions(0, 0)
5. |  item.checkPermission(Permission.CREATE)
6. |  commitQueue.add(item, Transactional.Action.CREATE)

The item has now been attached to the DbControl and added to the commit queue which means it will be processed by the next call to DbControl.commit(). The item has not been saved to the database after this call. What happens at commit time is outlined below.

3. Loading an existing item

The loading of an item is initialised by a client application calling the static AnyItem.getById() or some other method on the item class. The call then proceeds as follows:

1. AnyItem.getById(DbControl, id)
2. |  DbControl.loadItem(AnyItem.class, id)
3. |  |  load data object using Hibernate: data = HibernateUtil.loadItem(...)
4. |  |  DbControl.getItem(AnyItem.class, data)
5. |  |  |  check item cache if an item object already exists
6. |  |  |  if it does return that item, otherwise proceed
7. |  |  |  item = new AnyItem(data)
8. |  |  |  item.setDbControl(this)
9. |  |  |  item.initPermissions(0, 0)
10.|  |  |  item.checkPermission(Permission.READ)
11.|  |  |  add the item the item cache
12.|  |  |  add the item to the commit queue if it is Controlled using:
   |  |  |    commitQueue.add(item, Transactional.Action.UPDATE)
13.|  return item to client application

The item is now loaded and managed by the core. Any changes made to it will automatically be saved to the database when DbControl.commit is called. What happens at commit time is outlined below. If you do not want changes to be saved it is possible to detach the item. See below.

4. Deleting an existing item

It is possible to delete an existing item. Deletion is initialised by a client application by calling the DbControl.deleteItem() method. The call then proceeds as follows:

1. DbControl.deleteItem(item)
2. |  check that the item exists in the database (id != 0)
3. |  item.setDbControl(this)
4. |  item.initPermissions(0, 0)
5. |  item.checkPermission(Permission.DELETE)
6. |  commitQueue.add(item, Transactional.Action.DELETE)

The item has now been added to the commit queue which means it will be processed by the next call to DbControl.commit(). The item has not been deleted from the database after this call. What happens at commit time is outlined below.

5. Detaching an existing item

You must detach an item from the current session when you want to keep it in memory while interacting with the user. Ie. load item from database, detach it, display an edit item screen, reattach the item and commit. Detaching an item means that changes made to it will not be saved to the database unless it is reattached again. An item becomes detached when a client application calls DbControl.detachItem() or when the DbControl that loaded is closed. The call to the DbControl.detachItem() method proceeds as follows:

1. DbControl.detachItem(item)
2. |  remove data object from Hibernate first-level cache: HibernateUtil.evictData(...)
3. |  commitQueue.remove(item)
4. |  itemCache.remove(item.getData())
5. |  item.setDbControl(null)

A detached item can later be reattached. This will also reenable automatic saving of modification to the item. An item is re-attached when a client application calls the DbControl.reattachItem(item) method. The call the proceeds as follows:

1. DbControl.reattachItem(item)
2. |  check that the item exists in the database id != 0
3. |  item.setDbControl(this)
4. |  item.initPermissions(0, 0)
5. |  if the logged in user has write permission attach the item to
   |    the Hibernate session with update: HibernateUtil.updateData(...)
6. |  else if the logged in user has read permission attach the item to
   |    the Hibernate session with lock: HibernateUtil.lockData(...)
7. |  else throw a PermissionDeniedException
8. |  add the item to the item cache
9. |  add the item to the commit queue if it is Controlled

6. Committing a transaction

A transaction is committed when the client application calls DbControl.commit(). A commit processes items in the commit queue in the same order as they were entered. In the queue we find the following types of items:

The call to DbControl.commit() proceeds as follows:

1. DbControl.commit()
2. |  iterate the commit queue and...
3. |  |  ...call validate() on each Validatable item
4. |  |  ...call onBeforeCommit() on each Transactional item
5. |  |  ...call onBeforeCommit() on each new item and on items that should be deleted
6. |  |  ...call DbControl.updateDiskUsage() for each DiskConsumable item
7. |  |  ...save new items to the database: 
   |  |     HibernateUtil.saveData(...)
   |  |     add to item cache
   |  |     call onAfterInsert()
8. |  |  ...delete items that should be deleted:
   |  |     call isUsed() to check if the item is used
   |  |     HibernateUtil.deleteData(...)
   |  |     remove the item from the item cache
9. |  |  ...clear the commit queue from items that are not Controlled
10.|  HibernateUtil.commit(...)
11.|  if successful iterate the commit queue and...
12.|  |  ...call onAfterCommit() on each Transactional item
13.|  if there is an error call DbControl.rollback()

7. Rollback the transaction

A transaction cannot be explicitly rollbacked by a client application. A rollback only happens when a commit ends with an error or when the DbControl.close() method is called.

1. DbControl.close()
2. |  HibernateUtil.rollback(...)
3. |  HibernateUtil.close(...)
4. |  iterate the commit queue and...
5. |  ...call onRollback() on each Transactional item
6. |  empty commit queue and item cache and get rid of all Hibernate objects

8. Disconnecting and reconnecting the database

Sometimes you need to do a time-consuming operation while keeping a DbControl object in memory. For example, a file upload may take several minutes. In those cases it is a good idea to disconnect from the database to avoid hanging on to a database connection that is not used. You do this by calling the DbControl.disconnect() method. Under normal circumstances it is perfectly safe to use this method even if you have made changes to some items. The changes are always kept in memory until the commit() method is called.

The only exception to this rule is if you use any of the batch classes, ie. reporters. For performance reasons changes to those items are flushed to the database at regular intervals. If the connections is dropped those changes will be lost.

1. DbControl.disconnect()
2. |  HibernateUtil.disconnect(...)

Use the DbControl.reconnect() method to reconnect to the database again.

1. DbControl.reconnect()
2. |  HibernateUtil.reconnect(...)

9. Cleaning up

Normally everything is cleaned up when the DbControl.close() method is called, but we can never trust a client application to actually call it. Therfore we have added code in the finalize() method to call close. But this may not be enough. Remember that it is the SessionControl that creates the DbControl and that it is the SessionControl that contains all access permission information. If the SessionControl.logout() method is called we get into strange situation, if we continue to use the same DbControl object. It can get even more strange if the we login as another user.

To prevent this situation the SessionControl contains a cache of all DbControl objects that has been created and when the logout method is called it will also close all DbControl:s that are still open. The cache is using weak references in order to let the garbage collector do it's work when the client application has released the reference. For the same reason all other internal references to a DbControl (ie. from BasicItem and other objects are kept as weak references.