Ammentos allows automatically validating objects which are going to be saved
through the association of a Validator to the persistent class.
Validator is an abstract class with a single abstract method:
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:
10. Inheritance
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 A s domain,
and once into B s 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 with 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.IMPORTANT: use the attribute syncKeys=true
if you want Ammentos to synchronize current and parent keys while saving the object the first time.
11. Polymorphism
Ammentos' support for polymorphism is provided through type tables .
This mechanism makes possible to obtain from a query a List of
Superclass objects, which can be actually instances of subclasses
such as Subclass1 , Subclass2 etc. Here's how must be declared an
hypotethical superclass which is the root of a hierarchy.
@PersistentEntity(sourceDomain=" superclasses " , primaryKey=" id " ,
typeTable=" classtypes " , typeField=" classtype " , typeTableAutoCreate=true ,
typeTableAutoInsert=true )
public class Superclass {
@PersistentField(automatic=true )
private String id;
}
There are four new annotation attributes:
typeTable: the table in the database containing information about types
typeField: the field into the types table which contains the type name (the Java class name)
typeTableAutoCreate: determines if Ammentos must create the declared types table, if not already present in the database
typeTableAutoInsert: determines if Ammentos must insert type information into the types table if not already present
Once such a class is declared, you can declare and annotate its subclasses in this way:
@PersistentEntity(sourceDomain=" subclasses1 " , primaryKey=" id " ,
superKey=" superclass_id " ,
typeId=1, syncKeys=true )
public class Subclass1 extends Superclass{
@PersistentField(automatic=true )
private String id;
@PersistentField(fieldName=" superclass_id " )
private String superclassId;
}
As in the code above, subclasses do not need to re-declare typeTable related attributes, but just theyr typeId , an integer that must uniquely identificate the
current class in the types table defined for the SuperClass. The other attributes are those used for usual inheritance. Now a query like List lst = Ammentos.load(Superclass.class, new Query()); will return a List of items wich can be actually instances of Subclass1.
12. Working with multiple datasources
Since version 2.0 is possible to use Ammentos with multiple datasources at once.
This feature is provided from the new class PersistenceContext .
A persistenceContext wraps all information related to a specific dataSource and
works exactly as a separate Ammentos instance. Here's a simple snippet showing how to
instanciate and use a PersistenceContext
PersistenceContext context = Ammentos.createContext(myDataSource);
...
context.openTransaction();
...
context.save(myObject);
...
context.commitTransaction();
As shown in the code above, a PersistenceContext instance provides the same
methods as the old Ammentos facade class, but not static.
Of course is still possible continue using Ammentos in the static old way, so
backward compatibility is guarantee; by now using Ammentos in the static way
just means working with the default persistence context. Also it is
possible to use both notations ; for instance if you are modifing your
application which already used Ammentos you can now make it multiple-datasource-aware
just by adding a new PersistenceContext.
13. Using iterations
Another new feature coming with version 2.0 is the iterate() method.
Sometimes you can need to work with a big amount of data (for instance when
performing statistics applications). Before now using Ammentos for such
purposes was just impossible, because the old Ammentos.load() method could
bring out of memory your application if you tried to load big amounts of data.
Now Ammentos provides a very good performing iterate() method which returns
a pretty Iterable instance. At low level this iterable is tied to a
datasource, so that the data is not loaded in memory before it is needed.
This way you can now iterate millions of data without worring about OutOfMemoryErrors.
Let's a quick example showing a (not very smart) way to count all students
in our example database:
int count = 0;
for (Student s : Ammentos.iterate(Student.class, new Query())){
count++;
}
System.out.println(count);
...
Ammentos.closeIteration();
Of course the iterate() method can be used both statically (from Ammentos class)
and dynamically (through a PersistenceContext instance). Also notice how the
Ammentos.iterate() call can be used inside the
enhanced for loop
because it returns an Iterable<Student> instance.
Very important : in order
to release the resources the Ammentos.closeIteration() must be called
every time you finish an iteration. Ammentos will throw an exception if you
try to open a second Iterable from the same thread and PersistenceContext before
closing the opened iteration.
14. Composite primary keys
Since 1.2.3 version Ammentos includes a base support for composite primary keys.
Using this feature involves nothing new to learn: just declare the usual primaryKey
attribute and write the comma-separated list of all key fields, the same way as
using Java's
varargs
feature. Example:
@PersistentEntity(
sourceDomain = "lessons",
primaryKey = "teacher_id, course_id"
)
public class Lesson {
@PersistentField(fieldName="teacher_id")
private String teacher;
@PersistentField(fieldName="course_id")
private String course;
@PersistentField
private String topic;
// ...
}
Persisting and loading this kind of entities is the same as seen at par. 5 .
To simplify loading objects by keys a new load() method has been added which accepts
all key values. The order of the actual parameters must be the same declared
in the primaryKey attribute. For instance:
Lesson l = Ammentos.load(Lesson.class, "teacher1", "course1");
Limitations
This kind of entities have at the moment some limitations compared to
entities with simple primary keys. First of all they cannot be referred from
other entities: for instance is not possible to annotate a field (or a list, or a map)
of type Lesson into an entity. Also is not possible to use such entities as
base classes to derive other entities. A runtime exception will occur if you try
to do so. Hopefully future Ammentos' versions will overtake these limitations.
15. Persistence handlers
Since version 1.3.0 Ammentos provides three new annotations to interact with the
persistence flow. These annotations are:
@OnLoad
@OnSave
@OnDelete
The purpose of persistence handlers is to execute some code in the same code flow
of Ammentos' persisting methods, despite old event handlers which
are called in a different thread. This opens the door for a lot of applications
such as synchronizing data in memory, logging etc.
These new annotations are applied to methods , differently from the usual
mapping annotations which are applied to fields or class declaration.
Let's explain how these annotations work by a simple, but meaningful example.
...
@PersistentField
private Date m_timeStamp;
@OnSave(When.BEFORE)
private void updateTStamp(){
m_timeStamp = new Date();
}
The code above allows to update a TimeStamp of a PersistentEntity just before
the entity is saved. In other words, every time the entity is being saved, the
TimeStamp in the database is updated.
You can annotate this way as many methods you want in your class. Just remember
that these methods cannot receive parameters , but they can be public,
protected or private as well, depending on your needs.
Any catched exception thrown by these methods will be wrapped into a
PersistenceException returned from Ammentos' methods. Take in mind that, obviously,
this means that an exception thrown from an handler annotated with OnSave(When.BEFORE)
will prevent the object to be saved. NB: these is really not a good idea to
perform objects validation ; for these purposes use the Ammentos'
Validation mechanism instead.
Ammentos provides three parameters for @OnSave and @OnDelete, specified from the
enum called When :
The first two values are straightful; the last one means the annotated
method has to be called before AND after the persistence operation; which
means the same method will be executed twice.
The @OnLoad annotation instead has no attributes, just because BEFORE is
meaningless in this case (before loading it that's no entity to
call methods from).
16. LoadUnique
With version 1.3.0 Ammentos introduces a new loadUnique() method. This method
represents a shortcut for that cases when you are performing a query which you
are sure the returning object is only one. Before this version you had to
perform an Ammentos.load() call, check the list if it contained just one element
and get it from the list. Now all you have to write is something like this:
...
QueryFilter filter = new SqlQueryFilter("name=? and surname=?");
Person person = Ammentos.loadUnique(Person.class, filter);
Use this method just only if you are really sure the returning object is only one ;
you will get a PersistenceException if the query returns more than one element.
If the query returns no elements the method will just return null .
Appendix 1 - Field types
Name
Recommended SQL Type
Java type
BOOLEAN
BIT, BOOLEAN
boolean
DATE
DATE, TIMESTAMP
java.util.Date
ENTITY
The same SQL type of the referred entity's primary key
The class of the referred Entity (f.i. mypackage.MyClass )
INTEGER
INTEGER and compatible numeric types
int
LONG
BIGINT and compatible numeric types
long
NUMERIC
DOUBLE and compatible numeric types
double
STRING
All character types, such as CHAR, VARCHAR, LONGVARCHAR and so on
java.lang.String
It is highly recommended to use foreign keys in the tables which contain references used for
ENTITY type fields, in order to automatically have type consistence checkings from the DBMS.
Appendix 2 - Relations
Ammentos currently supports two kinds of objects relationships: one-to-one and
one-to-many. There are no special constructs for using them; you can just using
the well known annotations.
Below I provide a simle way to represent many-to-many relashionships too.
One to One : the most simple of relationships, it's easy performed by declaring
a field of type ENTITY into one class, as seen before. Using correct foreign keys declarations
in the database, this relation is perfectly represented in its natural way.
One to many : most of times it can be simply obtained by using PersistentLists.
By the way,generally speaking a one-to-many relashionship can involve, at the "many" side,
a huge amount of objects. If this is the case, the best solution can be to map the relation in the database
with 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 with 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 with 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 ) );
Many to many . 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;
}
Appendix 3 - Annotations details
@PersistentEntity
Name
Type
Description
Mandatory
Default value
Example
sourceDomain
java.lang.String
The domain (tipically a database table/view) from which instances of the annotated class
must be loaded
YES
-
"people"
targetDomain
java.lang.String
The domain (tipically a database table) against which instances of the annotated class must be saved
NO
The same as sourceDomain
"people"
primaryKey
java.lang.String
The name of the field which represents the primary key for the annotated class.
YES
-
"id"
superKey
java.lang.String
The name of the field containing the parent's primary key. Should be used when inheriting
a persistent class with another with a different primary key.
NO
The same as primary key
"parentId"
validator
java.lang.String
The fully specified class name of the validator for this class.
NO
-
"com.mypackage.MyValidator"
@PersistentField
Name
Type
Description
Mand.
Default value
Example
fieldName
java.lang.String
The name of the field into the persistence domain (tipically the name of the
table's field into the database)
NO
The name of the annotated member
"id"
description
java.lang.String
A descriptive representation of the annotated field. Can be useful for example
while using external tools.
NO
The field name
"product code"
typeClass
java.lang.String
The fully specified name of a user-defined FieldType.
NO
-
"com.aPckg.aType"
type
it.biobytes.ammentos. FieldTypeEnum
One of framework fieldtypes from the FieldTypeEnum enumeration.
NO
The fieldtype is determined from the member declaration.
FieldTypeEnum.INTEGER
automatic
boolean
If the value of the field must be automatically initialized from
the framework
NO
false
automatic=true;
automaticType
Ammentos. AutomaticType
Determines how the field has to be automatically generated. FRAMEWORK
means the value is created from Ammentos Framework; EXTERNAL for
externally generated values (ex. database sequences).
(Ignored if automatic=false)
NO
AutomaticType.FRAMEWORK
automaticType= AutomaticType.EXTERNAL;
external
boolean
Wheter the current field is managed externally (f.i. computed from another process)
and must not be overwritten by Ammentos during save() operations.
NO
false
external=true;
@PersistentList
Name
Type
Description
Mandatory
Default value
Example
query
java.lang.String
The query used to load the List. The syntax is the same used for SqlQueryFilter.
A "?" parameter representing the current instance must be present.
YES
-
"invoice_id=?"
itemsClass
java.lang.String
The fully qualified class name of the items contained in the List.
NO
The declaring type argument of the annotated List.
"com.aPckg.aDomain.Item"
sourceDomain
java.lang.String
The domain where to execute the loading query.
NO
The domain of the items contained in the list.
"Items"
cascadeOnSave
boolean
Tells if the elements in the list must be recursively saved when the parent
object is saved.
NO
true
cascadeOnSave=false
cascadeOnDelete
boolean
Tells if the elements in the list must be recursively deleted when the parent
object is deleted. Useful when modeling a composition.
NO
false
cascadeOnDelete=false
deleteOnRemove
boolean
Tells if the elements in the list must be persistently deleted when they are
removed from the list (composition). If set to false, removed objects which
were persistent are saved insted of being deleted, in order to make persistent
their changes (f.i. usually if their foreign key needs to be updated).
NO
true
deleteOnRemove=false;
@PersistentMap
Name
Type
Description
Mandatory
Default value
Example
query
java.lang.String
The query used to load the Map. The syntax is the same used for SqlQueryFilter.
A "?" parameter representing the current instance must be present.
YES
-
"invoice_id=?"
itemsClass
java.lang.String
The fully qualified class name of the items contained as values in the Map.
NO
The declaring type argument of values in the annotated Map.
"com.aPckg.aDomain.Item"
keyField
java.lang.String
The field to be used as key for the Map values. Must contain Comparable values .
NO
The primary key of the value class.
"id"
sourceDomain
java.lang.String
The domain where to execute the loading query.
NO
The domain of the items contained in the map.
"Items"
cascadeOnSave
boolean
Tells if the elements in the map must be recursively saved when the parent
object is saved.
NO
true
cascadeOnSave=false
cascadeOnDelete
boolean
Tells if the elements in the map must be recursively deleted when the parent
object is deleted. Useful when modeling a composition.
NO
false
cascadeOnDelete=false
deleteOnRemove
boolean
Tells if the elements in the map must be persistently deleted when they are
removed from the map (composition). If set to false, removed objects which
were persistent are saved insted of being deleted, in order to make persistent
their changes (f.i. usually if their foreign key needs to be updated).
NO
true
deleteOnRemove=false;
@OnSave, @OnDelete
Name
Type
Description
Mandatory
Default value
Example
value
When
When the callback will be performed during persistence calls
YES
-
OnSave(When.BEFORE)
Appendix 4 - Feedback
For any comments or suggestions about this guide please contact us .