The ObjectDatabase implementation makes things easier for the Java developer since the binding between Objects to Records is transparent.
OrientDB uses Java reflection and Javassist Proxy to bound POJOs to Records directly. Those proxied instances take care about the synchronization between the POJO and the underlying record. Every time you invoke a setter method against the POJO, the value is early bound into the record. Every time you call a getter method the value is retrieved from the record if the POJO's field value is null. Lazy loading works in this way too.
So the Object Database class works as wrapper of the underlying Document-Database.
NOTE: In case a non-proxied object is found it will be serialized, proxied and bounded to a corresponding Record.
Before to use persistent POJOs OrientDB needs to know which classes are persistent (between thousands in your classpath) by registering the persistent packages and/or classes. Example:
database.getEntityManager().registerEntityClasses("com.orientechnologies.orient.test.domain");
This must be done only right after the database is created or opened.
OrientDB follows some naming conventions to avoid writing tons of configuration files but just applying the rule "Convention over Configuration". Below those used:
Account
in the package com.orientechnologies.demo
, the expected persistent class will be "Account" and not the entire com.orientechnologies.demo.Account
. This means that class names, in the database, are always unique and can't exist two class with the same name even if declared in different packages.All the Java classes must have an empty constructor to let to OrientDB to create instances.
All your classes must have getters and setters of every field that needs to be persistent in order to let to OrientDB to manage proxy operations. Getters and Setters also need to be named same as the declaring field: Example:
public class Test {
private String textField;
private int intField;
public String getTextField() {
return textField;
}
public void setTextField( String iTextField ) {
textField = iTextField;
}
// THIS DECLARATION WON'T WORK, ORIENTDB WON'T BE ABLE TO RECOGNIZE THE REAL FIELD NAME
public int getInt(){
return intField;
}
// THIS DECLARATION WON'T WORK, ORIENTDB WON'T BE ABLE TO RECOGNIZE THE REAL FIELD NAME
public void setInt(int iInt){
intField = iInt;
}
}
To avoid ClassCastExecption when the Java classes have Collections and Maps, the interface must be used rather than the Java implementation. The classic mistake is to define in a persistent class the types ArrayList, HashSet, HashMap instead of List, Set and Map.
Example:
public class MyClass{
// CORRECT
protected List<MyElement> correctList;
// WRONG: WILL THROW A ClassCastException
protected ArrayList<MyElement> wrongList;
// CORRECT
protected Set<MyElement> correctSet;
// WRONG: WILL THROW A ClassCastException
protected TreeSet<MyElement> wrongSet;
// CORRECT
protected Map<String,MyElement> correctMap;
// WRONG: WILL THROW A ClassCastException
protected HashMap<String,MyElement> wrongMap;
}
OrientDB manages all the POJO attributes in persistent way during read/write from/to the record, except for the fields those:
OrientDB uses the Java reflection to discovery the POJO classes. This is made only once during the registration of the domain classes.
This is the default. It tries to use the getter and setter methods for the field if they exist, otherwise goes in RAW mode (see below). The convention for the getter is the same as Java: get<field-name>
where field-name is capitalized. The same is for setter but with 'set' as prefix instead of 'get': set<field-name>
. If the getter or setter is missing, then the raw binding will be used.
Example:
Field 'String name
' -> getName()
and setName(String)
Since v1.2 Orient provides the possibility of custom binding extending the OObjectMethodFilter class and registering it to the wanted class.
public boolean isHandled(Method m)
to let Orient know what methods will be managed by the ProxyHandler and what methods won't.public String getFieldName(Method m)
to let orient know how to parse a field name starting from the accessing method name.
In the case those two methods are not provided the default binding will be usedThe custom MethodFilter can be registered by calling OObjectEntityEnhancer.getInstance().registerClassMethodFilter(Class<?>, customMethodFilter);
Domain class example:
public class CustomMethodFilterTestClass {
protected String standardField;
protected String UPPERCASEFIELD;
protected String transientNotDefinedField;
// GETTERS AND SETTERS
...
}
Method filter example:
public class CustomMethodFilter extends OObjectMethodFilter {
@Override
public boolean isHandled(Method m) {
if (m.getName().contains("UPPERCASE")) {
return true;
} else if (m.getName().contains("Transient")) {
return false;
}
return super.isHandled(m);
}
@Override
public String getFieldName(Method m) {
if (m.getName().startsWith("get")) {
if (m.getName().contains("UPPERCASE")) {
return "UPPERCASEFIELD";
}
return getFieldName(m.getName(), "get");
} else if (m.getName().startsWith("set")) {
if (m.getName().contains("UPPERCASE")) {
return "UPPERCASEFIELD";
}
return getFieldName(m.getName(), "set");
} else
return getFieldName(m.getName(), "is");
}
}
Method filter registration example:
OObjectEntityEnhancer.getInstance().registerClassMethodFilter(CustomMethodFilterTestClass.class, new CustomMethodFilter());
You can read a POJO from the database in two ways:
load(ORID)
query(q)
When OrientDB loads the record, it creates a new POJO by calling the empty constructor and filling all the fields available in the source record. If a field is present only in the record and not in the POJO class, then it will be ignored. Even when the POJO is updated, any fields in the record that are not available in the POJO class will be untouched.
You can save a POJO to the database by calling the method save(pojo)
. If the POJO is already a proxied instance, then the database will just save the record bounded to it. In case the object is not proxied the database will serialize it and save the corresponded record: In this case the object MUST be reassinged with the one returned by the database
Starting from release 0.9.20, OrientDB supports Fetching-Strategies by using the Fetch Plans. Fetch Plans are used to customize how OrientDB must load linked records. The ODatabaseObjectTx uses the Fetch Plan also to determine how to bind the linked records to the POJO by building an object tree.
To let OrientDB use not supported types use the custom types. They MUST BE registered before domain classes registration, if not all custom type fields will be treated as domain classes. In case of registering a custom type that is already register as a domain class said class will be removed.
Important: java.lang classes cannot be managed this way
Example to manage an enumeration as custom type:
Enum declaration
public enum SecurityRole {
ADMIN("administrador"), LOGIN("login");
private String id;
private SecurityRole(String id) {
this.id = id;
}
public String getId() {
return id;
}
@Override
public String toString() {
return id;
}
public static SecurityRole getByName(String name) {
if (ADMIN.name().equals(name)) {
return ADMIN;
} else if (LOGIN.name().equals(name)) {
return LOGIN;
}
return null;
}
public static SecurityRole[] toArray() {
return new SecurityRole[] { ADMIN, LOGIN };
}
}
Custom type management
OObjectSerializerContext serializerContext = new OObjectSerializerContext();
serializerContext.bind(new OObjectSerializer<SecurityRole, String>() {
public Object serializeFieldValue(Class<?> type, SecurityRole role) {
return role.name();
}
public Object unserializeFieldValue(Class<?> type, String str) {
return SecurityRole.getByName(str);
}
});
OObjectSerializerHelper.bindSerializerContext(null, serializerContext);
// NOW YOU CAN REGISTER YOUR DOMAIN CLASSES
database.getEntityManager().registerEntityClass(User.class);
OrientDB will use that custom serializer to marshall and unmarshall special types.
Available since v1.0rc9
The ObjectDatabase implementation makes things easier for the Java developer since the binding between Objects to Records is transparent.
OrientDB uses Java reflection and doesn't require that the POJO is enhanced in order to use it according to the JDO standard and doesn't use Proxies as do many JPA implementations such as Hibernate. So how can you work with plain POJOs?
OrientDB works in two ways:
The ODatabaseObjectTx implementation is the gateway between the developer and OrientDB. ODatabaseObjectTx keeps track of the relationship between the POJO and the Record.
Each POJO read from the database is created and tracked by ODatabaseObjectTx. If you change the POJO and call the ODatabaseObjectTx.save(pojo)
method, OrientDB recognizes the POJO bound with the underlying record and, before saving it, will copy the POJO attributes to the loaded record.
This works with POJOs that belong to the same ODatabaseObjectTx instance. For example:
ODatabaseObjectTx db = new ODatabaseObjectTx("remote:localhost/demo");
db.open("admin", "admin");
try{
List<Customer> result = db.query( new OSQLSynchQuery<Customer>(db, "select from customer") );
for( Customer c : result ){
c.setAge( 100 );
db.save( c ); // <- AT THIS POINT THE POJO WILL BE RECOGNIZED AS KNOWN BECAUSE IS
// ALWAYS LOADED WITH THIS DB INSTANCE
}
} finally {
db.close;
}
When the db.save( c )
is called, the ODatabaseObjectTx instance already knows obout it because has been retrieved by using a query through the same instance.
In a typical Front-End application you need to load objects, display them to the user, capture the changes and save them back to the database. Usually this is implemented by using a database pool in order to avoid leaving a database instance open for the entire life cycle of the user session.
The database pool manages a configurable number of database instances. These instances are recycled for all database operations, so the list of connected POJOs is cleared at every release of the database pool instance. This is why the database instance doesn't know the POJO used by the application and in this mode if you save a previously loaded POJO it will appear as a NEW one and is therefore created as new instance in the database with a new RecordID.
This is why OrientDB needs to store the record information inside the POJO itself. This is retrieved when the POJO is saved so it is known if the POJO already has own identity (has been previously loaded) or not (it's new).
To save the Record Identity you can use the JPA @Id annotation above the property interested. You can declare it as:
Example:
public class Customer{
@Id
private Object id; // DON'T CREATE GETTER/SETTER FOR IT TO PREVENT THE CHANGING BY THE USER APPLICATION,
// UNLESS IT'S NEEDED
private String name;
private String surname;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getSurname(){
return name;
}
public void setSurname(String surname){
this.surname = surname;
}
}
OrientDB will save the Record Identity in the id property even if getter/setter methods are not created.
If you work with transactions you also need to store the Record Version in the POJO to allow MVCC. Use the JPA @Version annotation above the property interested. You can declare it as:
Example:
public class Customer{
@Id
private Object id; // DON'T CREATE GETTER/SETTER FOR IT TO PREVENT THE CHANGING BY THE USER APPLICATION,
// UNLESS IT'S NEEDED
@Version
private Object version; // DON'T CREATE GETTER/SETTER FOR IT TO PREVENT THE CHANGING BY THE USER APPLICATION,
// UNLESS IT'S NEEDED
private String name;
private String surname;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public String getSurname(){
return name;
}
public void setSurname(String surname){
this.surname = surname;
}
}
Since OrientDB doesn't know what object is changed in a tree of connected objects, by default it saves all the objects. This could be very expensive for big trees. This is the reason why you can control manually what is changed or not via a setting in the ODatabaseObjectTx instance:
db.setSaveOnlyDirty(true);
or by setting a global parameter (see Parameters):
OGlobalConfiguration.OBJECT_SAVE_ONLY_DIRTY.setValue(true);
To track what object is dirty use:
db.setDirty(pojo);
To unset the dirty status of an object use:
db.unsetDirty(pojo);
Dirty mode doesn't affect in memory state of POJOs, so if you change an object without marking it as dirty, OrientDB doesn't know that the object is changed. Furthermore if you load the same changed object using the same database instance, the modified object is returned.
In order to know which classes are persistent (between thousands in your classpath), you need to tell OrientDB. Using the Java API is:
database.getEntityManager().registerEntityClasses("com.orientechnologies.orient.test.domain");
OrientDB saves only the final part of the class name without the package. For example if you're using the class Account
in the package com.orientechnologies.demo
, the persistent class will be only "Account" and not the entire com.orientechnologies.demo.Account
. This means that class names, in the database, are always unique and can't exist two class with the same name even if declared in different packages.
All your classes must have an empty constructor to let to OrientDB to create instances.
All the POJO attributes will be read/stored from/into the record except for fields with the transient modifier. OrientDB uses Java reflection but the discovery of POJO classes is made only the first time at startup. Java Reflection information is inspected only the first time to speed up the access to the fields/methods.
There are 2 kinds of binding:
This is the default. It tries to use the getter and setter methods for the field if they exist, otherwise goes in RAW mode (see below). The convention for the getter is the same as Java: get<field-name>
where field-name is capitalized. The same is for setter but with 'set' as prefix instead of 'get': set<field-name>
. If the getter or setter is missing, then the raw binding will be used.
Example:
Field 'String name
' -> getName()
and setName(String)
This mode acts at raw level by accessing the field directly. If the field signature is private or protected, then the accessibility will be forced. This works generally in all the scenarios except where a custom SecurityManager is defined that denies the change to the accessibility of the field.
To force this behaviour, use the JPA 2 @AccessType annotation above the relevant property. For example:
public class Customer{
@AccessType(FIELD)
private String name;
private String surname;
public String getSurname(){
return name;
}
public void setSurname(String surname){
this.surname = surname;
}
}
You can read a POJO from the database in two ways:
load(ORID)
query(q)
When OrientDB loads the record, it creates a new POJO by calling the empty constructor and filling all the fields available in the source record. If a field is present only in the record and not in the POJO class, then it will be ignored. Even when the POJO is updated, any fields in the record that are not available in the POJO class will be untouched.
You can define some methods in the POJO class that are called as callbacks before the record is read:
Example:
public class Account{
private String name;
transient private String status;
@OAfterDeserialization
public void init(){
status = "Loaded";
}
}
Callbacks are useful to initialize transient fields.
You can save a POJO to the database by calling the method save(pojo)
. If the POJO is already known to the ODatabaseObjectTx instance, then it updates the underlying record by copying all the POJO attributes to the records (omitting those with transient modifier).
You can define in the POJO class some methods called as callback before the record is written:
Example:
public class Account{
private String name;
transient private Socket s;
@OAfterSerialization
public void free(){
s.close();
}
}
Callbacks are useful to free transient resources.
== Fetching strategies =v
Starting from release 0.9.20, OrientDB supports Fetching-Strategies by using the Fetch Plans. Fetch Plans are used to customize how OrientDB must load linked records. The ODatabaseObjectTx uses the Fetch Plan also to determine how to bind the linked records to the POJO by building an object tree.
To let OrientDB use not supported types use the custom types. Register them before to register domain classes. Example to manage a BigInteger (that it's not natively supported):
OObjectSerializerContext serializerContext = new OObjectSerializerContext();
serializerContext.bind(new OObjectSerializer<BigInteger, Integer>() {
public Integer serializeFieldValue(Class<?> itype, BigInteger iFieldValue) {
return iFieldValue.intValue();
}
public BigInteger unserializeFieldValue(Class<?> itype, Integer iFieldValue) {
return new BigInteger(iFieldValue);
}
});
OObjectSerializerHelper.bindSerializerContext(null, serializerContext);
// NOW YOU CAN REGISTER YOUR DOMAIN CLASSES
database.getEntityManager().registerEntityClass(Customer.class);
OrientDB will use that custom serializer to marshall and unmarshall special types.