Ammentos.setDataSource( myDataSource );
create table products ( id varchar(30) primary key, description varchar(200), [...] )
Annotations, also known as "Metadata" are a new, incredibly powerful feature which come whith the JDK5 of Java(TM). If you are new to the concept of Annotation, you can think at them as a way to add information to your code, which can be useful for external tools to perform any kind of operations. In this case Ammentos uses metadata information to elaborate how objects can be persisted.
By the way to use Ammentos you do not need to be a Metadata Guru; it's enough to be able to use just 2 annotation types: @PersistentEntity and @PersistentField.
For instance, there's a very simple example of Product class.
public class Product { private String m_id; private String m_description; public String getId() { return m_id; } public void setDescription( String description ) { m_description = description; } public String getDescription() { return m_description; } }
Notice that this class is a completely normal Java class. It does not extend any class nor implement any interface.
@PersistentEntity ( sourceDomain="products", targetDomain="products", primaryKey="id" ) public class Product
This declaration means that the class must be loaded from the products table and saved to the same table, and that the id field is its primary key. As you see source domain and target domain can be declared differently. This means that, for instance, you can load a class from a view and save it to a table. Furthermore, the paramer being a plain string, you can set the source domain to something like "products inner join magazine on[...]": by the way this is not recommended; using a view in this cases is he preferred way.
As a second step you must annotate each class member you want make persistent.
@PersistentField( fieldName="id", description="Product code", type=STRING, automatic=false ) private String m_id; @PersistentField( fieldName="description", description="Product description", type=STRING ) private String m_description;
The first annotation says that the annotated member represents the column "id" in the database and that it has to be traited as a STRING type from Ammentos persistors. It is also specified that is value must not be automatically initialized from the framework. The second annotation marks the class member as persistent into the "description" column.
Important: Each class annotated for Ammentos must have a default constructor (a constructor which does not take parameters). If you are worring about your code encapsulation, notice that this constructor can be even declared as private.
Ammentos.save( product );
Notice that you have a single save() method, and not dinstinct methods for inserting and updating obects. This is possible because Ammentos' database persistor takes trace of the objects loaded from the database, and is able to call the correct insert or update statement basing on this information.
Product product = Ammentos.load( Product.class, "product_1" );
To delete it just write this:
Ammentos.delete( product );
As every persistence layer, Ammentos provides a API for making queries. Ammentos querying API is represented from the Query class. You can think at an Ammentos Query like a collection of QueryFilter's instancies; in other words a query is made appending QueryFilters to a Query.
Ammentos provides 3 kinds of QueryFilters:
SimpleQueryFilter sqf = new SimpleQueryFilter( "name" ); sqf.setString( "Alan" );
CompositeQueryFilter cqf = new CompositeQueryFilter(); sqf = new SimpleQueryFilter( "surname" ); sqf.setString( "Stevens" ); cqf.addFilter( sqf ); sqf = new SimpleQueryFilter( QueryFilter.APP_OR, "surname" ); sqf.setString( "Ford" ); cqf.addFilter( sqf );
SqlQueryFilter sqlqf = new SqlQueryFilter( "name=? and surname=?" ); sqlqf.setString( "Alan" ); sqlqf.setString( "Ford" );
Query qry = new Query(); QueryFilter filter = query.appendFilter( "description" ); filter.setString( "my product" ); filter.setOperator( QueryFilter.OP_NOTEQUALS ); List<Product> products = Ammentos.load( Product.class, qry );
Even if it can look something odd is possible to use every kind of QueryFilters in the same query.
Because SqlQueryFilter is probably the most useful of QueryFilters in normal programming, as it can be used to generate almost complete queries, the Query class provides a constructor which directly takes a SqlQueryFilter. This makes it possible to write code like this below:
SqlQueryFilter sqlqf = new SqlQueryFilter( "name=? and surname=?" ); sqlqf.setString( "Alan" ); sqlqf.setString( "Ford" ); List<Person> lp = Ammentos.load( Person.class, new Query( sqlqf ) );
qry.setDomain( "(select * from people where name='Alan') as Alan_named" );
try { Ammentos.openTransaction(); //[...] Ammentos.save( obj1 ); //[...] Ammentos.save( obj2 ); Ammentos.commitTransaction(); } catch( SomeKindOfException e) { try { Ammentos.rollbackTransaction(); } catch( PersistenceException ex ); }
As a lightweight framework Ammentos is designed to be used in a server environment as in a desktop client application. This is why Ammentos supports event driven programming.
To receive notification every time an object is saved or deleted through the framework class you have to implement the PersistenceListener interface and register your object instance to the framework by calling the addPersistenceListener() method.
The PersistenceListener interface declares two methods:
public void objectSaved( PersistenceEvent e ); public void objectDeleted( PersistenceEvent e );
Ammentos allows automatically validating objects which are going to be saved through the association of a Validator to the persistent class.
public abstract void performValidation( E obj );
package it.biobytes.ammentos.test; import it.biobytes.ammentos.*; import it.biobytes.ammentos.validation.*; public class PersonValidator extends Validator<Person> { public void performValidation( Person obj ) { checkTrue( ( obj.getName() != null ) && ( !Character.isDigit( obj.getName().charAt( 0 ) ) ), "The name cannot be null and cannot start whit a digit" ); checkTrue( ( obj.getSurname() != null ) && ( !Character.isDigit( obj.getSurname().charAt( 0 ) ) ), "The surname cannot be null and cannot start whit a digit" ); } }
Once executed a Validator generates a ValidationReport from which Ammentos will be able to decide if an Object can be saved or not. If an object validation returns a not empty ValidationReport the object will not be saved and a PersistenceException will be thrown when Ammentos.save() method is called on this object. But all this work is done behind the scenes from Ammentos. The only one thing you have to do to ensure your objects will pass through this validation process is assign your validator in the PersistentEntity annotation. F.i. a Person class will be annoted in this way:
@PersistentEntity ( sourceDomain="people", targetDomain="people", primaryKey="id", validator="it.biobytes.ammentos.test.PersonValidator" ) public class Person {[..]
Inheritance into Ammentos is managed in a nearly totally transparent way. This means that you can build the inheritance hierarchy you prefer whitouth almost troubling about Ammentos. The concept to keep in mind is that, as in OOP an object B which extends A "is a" A, in the same way saving a B object means saving it "as a" A object and "as a" B object. More precisely, a B object is saved twice, once into the As domain, and once into Bs domain.
To be able to perform these kind of operations Ammentos needs to know how to obtain information about the parents domain from the current class. The way used from Amentos to obtain this information is by examining the superKey parameter into PersistentEntity annotation. If not specified, the superKey will be the same as the primary key. This means that for Ammentos the superclass and the current class will have the same primary key. This is the simplest case and an example is represented here:
@PersistentEntity ( sourceDomain="teachers", targetDomain="teachers", primaryKey="id" ) public class Teacher extends Person { @PersistentField( fieldName="id", description="code", type=STRING ) private String m_id; [...]
In the code above Teacher class extends the Person class, and maintains the field "id" as primary key. Notice that the id field needs to be redefined in the subclass, because it is managed separately from the id field of the Person class. So when Ammentos is called to save a Teacher object whith a primary key of "testPerson" it will save, in sequence, a Person whith a primary key of "testPerson" and a teacher whith a primary key of "testPerson".
In most cases you will want a different primary key for a subclass. For instance in a school is better to manage students through their school code instead of their person code. In this case you will simply have to specify the superKey into the subclass, which is a field, stred into the subclass itself, which will contain the primary key of the superclass. This case is shown in the code below:
@PersistentEntity ( sourceDomain="students", targetDomain="students", primaryKey="code", superKey="person_id" ) public class Student extends Person { @PersistentField( fieldName="code", description="student code", type=STRING ) private String m_code; @PersistentField( fieldName="media", description="student media", type=INTEGER ) private int m_media; @PersistentField( fieldName="person_id", description="person_id", type=STRING ) private String m_personId; /** Creates a new instance of Student */ public Student( String code, String personId ) { super( personId ); m_personId = personId; m_code = code; }
In the code above the Student class defines its own primary key ("code") which is different from Person's primary key ("id"). To allow Ammentos correctly load a Student "as a" Student and "as a" Person it is so necessary to define a field into Student class which will contain the primary key "as a" Person too. This is the mean of the superKey declaration.
Ammentos does not provide special constructs to support Objects relationships. This is because the author has not yet found out a method for doing so that would really simplify developers life instead of making it more complicated. By the way relating objects each other is still very simple using Ammentos. Let's explain how:
Generally speaking a one-to-many relashionship can involve, at the "many" side, a huge amount of objects. In this case the best solution is to map the relation in the database whith a foreign key, but to never load all objects into the classe representing the "one" side of the relation. Instead, a portion of these objects can be easily loaded, when needed, through a simple query. For Instance by using a SQLQueryFilter like this one:
SQLQueryFilter sql = new SQLQueryFilter( "object_id=?" ); sql.setObject( myObject, ENTITY.getFieldType() );
If otherwise the "many" side of the relation is guarantee to always be a raisonable amount of objects, Ammentos provides a commodity method for working whith these objects: the loadUpdatable() method. The list returned from this method, although externally represented as a normal list, is an instance of a special type of List called PersistentList. PersistentList overrides normal List methods to make its operations persistent. This means that, when a remove() operation is performed, the removed object will also be deleted from the database; when add() is called the object is also saved into the database, and so on.
So one to many relashionships can easily be implemented by declaring a field, into the object representing the "One" side, of List type, which is initialized whith a loadUpdatable() call. Of course, this field won't be annotated (and in facts there's no way to annotate it in a correct way). This is explained in the example below:
private List<MyObject> myObjects; //... myObjects = Ammentos.loadUpdatable( MyObject.class, new Query( sql ) );
This kind of relation is tipically obtained using a reference table into the database, which rows contains primary keys of the related objects. So, actually, each single in the database represents a simple one to one relation. The most simple thing to do is find (or add) a column containing the primary key for each row, and implement a class containing two one to one relations, as seen at the first point. This case is explained in the code below.
@PersistentEntity ( sourceDomain="contracts", targetDomain="contracts", primaryKey="id" ) public class Contract { @PersistentField( fieldName="id", description="Contract id", type=STRING ) private String m_id; @PersistentField( fieldName="seller_id", description="Seller", type=ENTITY ) private Customer m_seller; @PersistentField( fieldName="acquirer_id", description="Acquirer", type=ENTITY ) private Customer m_acquirer; }