This article reviews the APIs for working with relational persistent data in Backendless. By the end of the article, you will have a working application which demonstrates various mechanisms for loading related data objects from the backend-as-a-service data store. To avoid any terminology confusion or overlap, let’s define related data as multiple objects referencing one another in the object-oriented data model. For example, suppose there is a class called Order. The class aggregates (references) a collection of OrderItem’s. This is a one-to-many relation. The OrderItem class has a one-to-one relation with a class called Manufacturer. Consider the following diagram:
Java implementation of the class diagram shown above may look like this:
Order.java:
package com.backendless.sample; import java.util.ArrayList; import java.util.Date; import java.util.List; public class Order { private String objectId; private String name; private List<OrderItem> items; private Date created; private Date updated; public String getObjectId() { return objectId; } public void setObjectId( String objectId ) { this.objectId = objectId; } public Date getUpdated() { return updated; } public void setUpdated( Date updated ) { this.updated = updated; } public String getName() { return name; } public void setName( String name ) { this.name = name; } public void addItem( OrderItem item ) { if( items == null ) items = new ArrayList<OrderItem>(); items.add( item ); } public List<OrderItem> getItems() { return items; } public void setItems( List<OrderItem> items ) { this.items = items; } public Date getCreated() { return created; } public void setCreated( Date created ) { this.created = created; } @Override public String toString() { return "Order{" + "name='" + name + '\'' + ", items=" + items + ", created=" + created + ", updated=" + updated + '}'; } }
OrderItem.java
package com.backendless.sample; public class OrderItem { private String itemName; private double price; private Manufacturer manufacturer; public String getItemName() { return itemName; } public void setItemName( String itemName ) { this.itemName = itemName; } public double getPrice() { return price; } public void setPrice( double price ) { this.price = price; } public Manufacturer getManufacturer() { return manufacturer; } public void setManufacturer( Manufacturer manufacturer ) { this.manufacturer = manufacturer; } @Override public String toString() { return "OrderItem{" + "itemName='" + itemName + '\'' + ", price=" + price + ", manufacturer=" + manufacturer + '}'; } }
Manufacturer.java
package com.backendless.sample; public class Manufacturer { private String name; private String city; public String getCity() { return city; } public void setCity( String city ) { this.city = city; } public String getName() { return name; } public void setName( String name ) { this.name = name; } @Override public String toString() { return "Manufacturer{" + "name='" + name + '\'' + ", city='" + city + '\'' + '}'; } }
The classes above will be used in the example. A complete project will all the functionality reviewed in this article is available in the Backendless Github repository.
We will be reviewing Backendless Data Service APIs in this article and you are welcome to follow along. For this you will need a Java IDE so you can build the client-side of the program and also setup a Backendless backend. For the IDE part, you can use the project from our Github repository or create a new one. For the latter, make sure to import backendless.jar which you can obtain from the Backendless SDK for Android/Java.
To setup the backend:
Backendless.initApp( "PUT-YOUR-APP-ID-HERE", "PUT-YOUR-SECRET-KEY-HERE", "v1" );
Android program:
Backendless.initApp( context, "PUT-YOUR-APP-ID-HERE", "PUT-YOUR-SECRET-KEY-HERE", "v1" );
The API call must be placed somewhere the begging of the program before any other code runs.
In order to see how related objects can be retrieved from the persistent storage, it must be populated with data. Backendless storage maps a table for each class. Therefore there will be 3 tables containing objects of the corresponding classes: Order, OrderItem and Manufacturer. There are two ways to add data to your Backendless backend:
private static void setupData() throws Throwable { Manufacturer microsoft = new Manufacturer(); microsoft.setName( "Microsoft" ); microsoft.setCity( "Redmond" ); OrderItem orderItem1 = new OrderItem(); orderItem1.setItemName( "XBOX One" ); orderItem1.setPrice( 399 ); orderItem1.setManufacturer( microsoft ); OrderItem orderItem2 = new OrderItem(); orderItem2.setItemName( "Surface Pro" ); orderItem2.setPrice( 899 ); orderItem2.setManufacturer( microsoft ); Order order = new Order(); order.setName( "Birthday present" ); order.addItem( orderItem1 ); order.addItem( orderItem2 ); Backendless.Data.of( Order.class ).save( order ); Manufacturer apple = new Manufacturer(); apple.setName( "Apple" ); apple.setCity( "Cupertino" ); OrderItem macbook = new OrderItem(); macbook.setItemName( "MacBook" ); macbook.setPrice( 1200 ); macbook.setManufacturer( apple ); OrderItem iphone = new OrderItem(); iphone.setItemName( "iPhone" ); iphone.setPrice( 599 ); iphone.setManufacturer( apple ); Order appleStuff = new Order(); appleStuff.setName( "Order for office" ); appleStuff.addItem( macbook ); appleStuff.addItem( iphone ); Backendless.Data.of( Order.class ).save( appleStuff ); }
Once the data is imported through the console or created with the code, it should look as in the image below. This is a screenshot from the Backendless console’s Data screen. Feel free to browse around the created tables and inspect the relations between the data objects.
The most basic way to load saved objects from Backendless uses the following API:
BackendlessCollection<T> collection = Backendless.Data.of( T ).find();
where T is the class designating the type of objects to load. For example, to load Order objects, you could use the following code:
private static void basicDataLoad() { BackendlessCollection<Order> orders = Backendless.Data.of( Order.class ).find(); for( Order order : orders.getCurrentPage() ) System.out.println( order ); }
This produces the following output:
Order{name='Order for office', items=null, created=Tue May 27 15:02:51 CDT 2014, updated=null} Order{name='Birthday present', items=null, created=Tue May 27 15:02:50 CDT 2014, updated=null}
Notice that with the basic data loading mechanism (the find() method without any arguments), the objects are loaded without any related data – none of the Order objects has the “items” collection initialized. The sections below describe how to retrieve these related objects.
With this approach, related objects can be retrieved along with the parent instances without any changes in the calling program. Exactly the same code, as shown above, can retrieve some or all related objects. take a look at the Order objects in Backendless console. Specifically, notice the header of the “items” column:
Selecting the “auto load” checkbox instructs the Backendless backend to automatically pre-populate the specified relation when the parent object is retrieved with an API call. For example, when the checkbox shown above is selected, the method shown above produces the following result:
Order{name='Order for office', items=[OrderItem{itemName='MacBook', price=1200.0, manufacturer=null}, OrderItem{itemName='iPhone', price=599.0, manufacturer=null}], created=Tue May 27 15:02:51 CDT 2014, updated=null} Order{name='Birthday present', items=[OrderItem{itemName='Surface Pro', price=899.0, manufacturer=null}, OrderItem{itemName='XBOX One', price=399.0, manufacturer=null}], created=Tue May 27 15:02:50 CDT 2014, updated=null}
The auto-load option can be applied to any relation at any level. Notice that the output above does not include the “manufacturer” property. This can be corrected by selecting the “auto load” checkbox for that relation in the OrderItem table.
As the name suggests, this approach allows to pre-initialize specific relations when loading the parent object. For example, the code below loads a collection of Order objects and requests that both “items” and “manufacturer” relations to be pre-initialized in the returned Order instances:
private static void loadAllOrdersWithAllRelations() { BackendlessDataQuery query = new BackendlessDataQuery(); QueryOptions queryOptions = new QueryOptions(); queryOptions.addRelated( "items" ); queryOptions.addRelated( "items.manufacturer" ); query.setQueryOptions( queryOptions ); BackendlessCollection<Order> orders = Backendless.Data.of( Order.class ).find( query ); for( Order order : orders.getCurrentPage() ) System.out.println( order ); }
The code produces the following output:
Order{name='Order for office', items=[OrderItem{itemName='MacBook', price=1200.0, manufacturer=Manufacturer{name='Apple', city='Cupertino'}}, OrderItem{itemName='iPhone', price=599.0, manufacturer=Manufacturer{name='Apple', city='Cupertino'}}], created=Tue May 27 15:02:51 CDT 2014, updated=null} Order{name='Birthday present', items=[OrderItem{itemName='Surface Pro', price=899.0, manufacturer=Manufacturer{name='Microsoft', city='Redmond'}}, OrderItem{itemName='XBOX One', price=399.0, manufacturer=Manufacturer{name='Microsoft', city='Redmond'}}], created=Tue May 27 15:02:50 CDT 2014, updated=null}
With this approach, a parent object is loaded first and then there is a separate API call to load specific related objects into the given parent object. For instance, the example below fetches an Order object and then loads the “items” relations for the Order object using the loadRelations method.
private static void loadRelations() throws Throwable { Order firstOrder = Backendless.Data.of( Order.class ).findFirst(); System.out.println( "Order before relation is loaded - " + firstOrder ); ArrayList<String> relationProps = new ArrayList<String>(); relationProps.add( "items" ); relationProps.add( "items.manufacturer" ); Backendless.Data.of( Order.class ).loadRelations( firstOrder, relationProps ); System.out.println( "Order after relation is loaded - " + firstOrder ); }
Since the relationProps is a collection of strings, it can accommodate multiple relation properties (as shown in the example above). Specifically, notice the “items.manufacturer” property refers to the “manufacturer” relation in the OrderItem class. The code above produces the following output:
Order before relation is loaded - Order{name='Birthday present', items=null, created=Tue May 27 15:02:50 CDT 2014, updated=null} Order after relation is loaded - Order{name='Birthday present', items=[OrderItem{itemName='Surface Pro', price=899.0, manufacturer=Manufacturer{name='Microsoft', city='Redmond'}}, OrderItem{itemName='XBOX One', price=399.0, manufacturer=Manufacturer{name='Microsoft', city='Redmond'}}], created=Tue May 27 15:02:50 CDT 2014, updated=null}
This approach can be combined with the “auto-load” option described above.
Quite often an application needs to load only a specific subset of child objects. For example with the dataset used in this article, it may be required to load all the order items where the price is greater than a certain value. Consider the example below. The code in this example loads the order items for a specific order with the condition specified in the query:
private static void loadOrderItemsWithQuery() throws Throwable { Order firstOrder = Backendless.Data.of( Order.class ).findFirst(); String queryString = "Order[items].objectId = '" + firstOrder.getObjectId() + "' and price > 400"; BackendlessDataQuery query = new BackendlessDataQuery( queryString ); BackendlessCollection<OrderItem> orderItems = Backendless.Data.of( OrderItem.class ).find( query ); for( OrderItem orderItem : orderItems.getCurrentPage() ) System.out.println( orderItem ); }
The queryString value uses a special format which we came up with to allow child-to-parent references. Notice the code loads objects from the OrderItem table (the argument of the “of” method is OrderItem.class:
BackendlessCollection<OrderItem> orderItems = Backendless.Data.of( OrderItem.class ).find( query );
The first part of queryString is “Order[items]”. The way you should read this is: the table from which the data is loaded is always referenced in the brackets expressed as a property of the parent object: “[items]”. To the left from that is a level-up lookup to the parent object: “Order”. Thus you can reference the parent object’s properties as it is done in the query: “Order[items].objectId” which means: take “objectId” from the Order object, which is a parent for the table where the backend runs the find operation – “[items]” or OrderItem.