| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This chapter describes how different aspects of your object model are mapped to the relational database. This knowledge is not required to use the library, these mechanisms are all automatic and non-configurable anyway.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
All object operations are available from the Store object. As you probably observed, there is no
separate save(), and insert(), only save(). Objects which have not yet been inserted
will be inserted by this method, and already existing objects will be saved.
The method is not transitive most of the time, it is only transitive on paths of non-existing objects. This means, that when you save an object, the referred objects (either directly through a reference, or inside Maps or Lists) will not be saved with this object, except, when that object does not exists yet. The idea is, that when you explicitly save an object, the library assumes, that you want to save the object as is, with it's attributes, and current relations to other objects. Because a relation can only be saved, if both ends of the relation exist, all objects to which this object relates will be saved. All already existing objects will not be saved, because that is not necessary for the relation to be saved. This algorithm is capable of handling circular references, self-references so no infinite recursion can occur.
When an existing object is saved, it is important to note, that it is saved as is. Suppose, an object is queried from BeanKeeper, and is presented to the user. Let's suppose that another thread modifies this object in the background (this is possible, if no locks are used). Let's say, that the user reviewed the object, but did not alter any of the attributes. If she clicks save, what should happen? None of the attributes have changed, but it would be quite confusing, if the object came back with the modifications of the background thread. Instead, BeanKeeper detects that some attributes were indeed changed in the background, and that the object's attributes need to be saved, even if no physical changes took place on the object itself! So in the end, the 'older' object will became the current object again, overwriting the background modification.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The main nodes of your object model are objects themselves. All objects which are conforming to a few simple rules can be handled by the library:
Store methods.
Basically, what these rules are saying is, that you should create your on class hierarchy, and then you'll be fine. Note however, that when extending some java.** class, attributes in those classes will not be saved, only those that are outside the java.** hierarchy.
Objects with classes which conform to the above can be saved, searched for or removed. When the library encounters such a class the first time, a table will be created for the class. The table's name will mostly match the class' name, more specifically, the library will try to create a table with the classname but the packages stripped. If that is already taken, the library will extend the name with more and more package names until the name becomes unique. Of course it is possible that the name will be too long for the database to handle (yes, such databases exist even today), in this case the library will try to guess a name using the classname and some numbers appended to it.
Each such table will hold an object of the class it was created for in a single row, so the table will have a column for each attribute in the class. The library will examine the class using reflection, and automatically create columns for each non-static non-transient member attribute.
For example a class like this:
package com.acme.bookstore;
public class Book
{
private String title;
private String authorName;
private Date publishDate;
private int quantity;
...getters, setters...
}
|
Will cause the library to create a table named book, with the following columns:
Column | Type |
|---|---|
title | text |
authorName | text |
publishDate | timestamp |
quantity | int |
There will be other columns however, with metadata specific to the persistence library, these will be prefixed with persistence_. The primary key of the table will consist of the combinations of some of these meta-data. The library will assign each object an identifier called the persistence_id. This information will be also saved with each object into their rows, but keep in mind, that this is not a unique identifier. It identifies an object alright, but there are multiple versions of the same object concurrently in the database, so the primary key of the tables are the persistence_id and the version information together.
As we saw, all primitive types are directly mapped to their respective columns in the database. All following primitive types and their boxed counterparts are supported:
Additionally the following types are also handled like primitive types, so they are directly mapped to a column in a table:
In each database these fields are mapped to a specific sql type best suited for storing the given java type. There are a few guarantees which can be taken granted, whatever database is in use:
All these types are guaranteed to be saved exactly with the value they were given. That is, boxed types can always be null, in which case they will stay null when saved and loaded from database. All primitive types are guaranteed to have the exact same value after reload expect float and double, which can have minor errors due to differing floating point representations by the database.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The library is capable of handling Collection and Map typed attributes also, so all attributes which are
declared Collection, Set, List or Map are supported.
If an object has some Collection attributes, the library will of course not include the whole collection into one column,
but it will create a sub-table for this attribute. A relation table, that will connect the container object with
the objects in the list. The container types behave exactly as defined by the Java API specification: Sets are
unordered and can contain no duplicate objects (objects with same persistence_id), attributes declared
Collection will be implemented as Sets, Lists are ordered (they maintain order!) and can contain the
same object multiple times. All container types can contain other objects, or primitive types, but they can not
contain other container types, or null values. Additionally Maps can only have String keys.
For the library to recognize these types, the declaration has to be with the exact types Collection, Set, List or Map:
public class Book
{
private String title;
private List authors;
}
|
This is because the library will replace ordinary Vector, ArrayList and other specific class instances
with special, own objects. These objects will implement on-demand and lazy loading features but will behave
exactly as the interface specifications describe. Be sure to avoid casting these objects, because they will
most likely throw ClassCastException.
Note however, that from the point of view of BeanKeeper, these container types are containing relations.
If you call for example contains() method on some List, it will not use the object's own equals()
method, but it will determine whether the relation to the given object is present in the List.
If you try to remove() and object from a container, you do not need to have the exact object
that was inserted, because only the object's identity is important to remove the relation from it. This also
means, that, for example a Set can contain two objects, which may equal in Java sense, but if
they have different identities (persistence_ids), then they both can be added to the same Set.
The only exception from the above rules are primitive types. When invoking contains() or indexOf()
methods with primitive objects, then the value is used for search, and not the primitive object's
persistence_id.
To summarize, the library handles attribute Lists and Maps with a table which contains the
relations necessary to select the objects in the List or Map. This is suitable for handling
many-to-many relations, which means an object can be a member in a list within multiple objects, and an object
can have multiple contained objects in a list. Some might formulate the question how the library detects
one-to-many, many-to-one patterns. Well, it doesn't. It treats everything as many-to-many, which of course
creates an overhead when selecting, but significantly simplifies the handling of these relations.
Note on loading: Collection types and Map attributes will load lazyly. That is, they will not be
loaded together with the object, but only when they are the first time referenced. And even if they are
referenced, they will never contain more than a few dozen objects at a time from the database. So they
behave just like LazyLists (because they are backed by one by the way). This means you can
use these constructs to hold virtually unlimited amount of data, either one-to-many or many-to-many relations.
Check the appendices for a description of the efficiency of these container types and their methods.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Bean type attributes are attributes which represent a one-to-one (or one-to-many) relation to another object. These are handled with storing the persistence_id simply in the column representing the attribute in question in the database. When loading objects with direct references to other objects, these referred objects will be always loaded immediately with the referrer object itself. Nulls are allowed in such attributes, and will stay null when saved and reloaded from database.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
With polymorphism, selecting for a superclass, you assume that the result list will contain all objects which are subclasses of the selected superclass. Take the following example:
public class Writing
{
private String title;
private Author author;
...getters, setters...
}
public class Book extends Writing
{
private String isbn;
...getter, setter...
}
|
In this example, if you query the database for a Writings with an author specified, you might assume
that the result list will contain Writings and also Books which the specified author might have
written.
In Java, an inheritance graph of a single class is always a tree, because every class has exactly one superclass
(except the java.lang.Object which has none), and every class might have zero or more subclasses. To
represent a class in the database, the library creates a table for the class, as described in the previous
sections. When the class has a superclass, which is also an ordinary bean, then the library creates a table
for that class too. That means each class has it's own table, which has columns for those attributes defined
in that class, but not for attributes which are merely inherited. When selecting for a class that is polymorphic, the
library knows, that it is contained in more tables, and joins together these tables to recevie all columns which
are accessible in a potential object of that class.
So, following the example above, the Book class will cause a table named book, but it will have only
one attribute: isbn. The writing table will have the title and author attributes.
Polymorphism also enabled for member attributes to be interface classes, for example:
public class Tree
{
private GraphNode root;
...getters, setters...
}
public interface GraphNode
{
List getParents();
List getChildren();
}
|
In an extreme case, you can define a member attribute to be java.lang.Object, however, the less
specified the attribute, the more complex the query. Even the number of the select statements to
execute is dependant on the specified attribute's class.
| [ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
We previously described, how the library handles one-to-one relations (references). One-to-many and many-to-many relations are not distinguished automatically by the library, as these can't be detected reliably without any user intervention.
Detecting one-to-many relations automatically would mean, that there is a List in the parent object, and when you insert another object into it, the library somehow guesses that there is an attribute in that object which represents this relationship, and that attribute should be used to insert the object into the list, and the list itself should be selected using that attribute. This is simple using annotations, but the library does not want to use any kind of configuration. Instead, you have two choices:
You can simply use any of the suitable container types. As you recall, these container types handle many-to-many relations, so practically you may lose a bit of performance, since instead of storing the one-to-many relation, you just store it, as it were a many-to-many relation. Some care about that lost performance, and some care about simple code, it's your choice. Anyhow, if one were to store events which are related to a person, one could simply write:
public class Person
{
private List events;
public List getEvents()
{
return events;
}
public void addEvent(Event event)
{
events.add(event);
}
}
public class Event
{
private int type;
private Object parameter;
...getter, setter...
}
|
The other option is to manually create the relation. This means to insert a Person attribute in the event, and assemble the query manually:
public class Person
{
public List getEvents()
{
return getStore().find("find event where owner = ?", new Object{this});
}
public void addEvent(Event event)
{
event.setOwner(this);
getStore().save(event);
}
}
public class Event
{
... attributes ...
private Person owner;
...getter, setter...
}
|
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Robert Brautigam on November, 21 2009 using texi2html 1.78.