Problem updating relation one-to-many

Hi!

I have two tables, A and B with the following feature.

Table A has a column called B_OBJECTS, which relates an object of table A with one or more objects of table B.
So there is a relation one to many between A and B, described by the column B_OBJECTS in table A.

When i try to update the list of B objects in B_OBJECTS, i have a weired behaviour.
Here is an example

    if the object in A has two objects in B_OBJECTS and i want to replace these with two other objects, the result of the updating operation is that B_OBJECTS contains a list of 4 B objects.

In the updating function i create a new element of A specifying the same object id of the element that i want to update, then i create the list of B_OBJECTS with the new two B elements and finally i save the A element.
This is the pseudo-code.

A a = new A();
a.setObjectId(otherObjectID);
B b1 = new B();
// fill b1
B b2 = new B();
//fill b2
List<B> B_OBJECTS = new List<B>();
B_OBJECTS.add(b1);
B_OBJECTS.add(b2);
a.setB_OBJECTS(B_OBJECTS);
A savedA = Backendless.Persistence.save(a);

B_OBJECTS in savedA has 4 elements and not 2 as i would like to.

Could you explain me the reason of such a behaviour?

Thank you

Hi,

Internal ticket ( BKNDLSS-12742 ) has been created for this issue. As a workaround tou can unlink current related objects and than save new relation.

Regards,

Denys

Thank you for your help.

In order to allow you understanding better my situation, here is an example of what I’m doing in order to update my records on the DB.
The following code represents

    Database models Custom server code
The Database models used in this case for the Car table and Person table:
package com.example.service;

import com.backendless.Backendless;
import com.example.models.custom.Constants;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public class Car {

    private String name;
    private String colour;

    private String ownerId;
    private String objectId;
    private java.util.Date created;
    private java.util.Date updated;

    public Car() {
    }

    /**
     * The constructor gets a JSON formatted string and retrieve the information fron it in order to build the onbject.
     * The method is used both during the creation and the update.
     *
     * @param jsonString Contains JSON formatted data of the class.
     */
    public Car(String jsonString) {

        try {

            JSONTokener jsonTokener = new JSONTokener(jsonString);
            JSONObject car = (JSONObject) jsonTokener.nextValue();
            DateFormat dateFormat = new SimpleDateFormat(Constants.DATE_FORMAT);

            if (car.has("name"))
                this.name = car.getString("name");
            if (car.has("colour"))
                this.colour = car.getString("colour");

            if (car.has("ownerId"))
                this.ownerId = car.getString("ownerId");
            if (car.has("objectId"))
                this.objectId = car.getString("objectId");
            if (car.has("created"))
                this.created = dateFormat.parse(car.getString("created"));
            if (car.has("updated"))
                this.updated = dateFormat.parse(car.getString("updated"));

        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    // Getters

    public String getName() {
        return this.name;
    }

    public java.util.Date getCreated() {
        return this.created;
    }

    public String getOwnerId() {
        return this.ownerId;
    }

    public String getObjectId() {
        return this.objectId;
    }

    public String getColour() {
        return this.colour;
    }

    public java.util.Date getUpdated() {
        return this.updated;
    }

    // Setters

    public void setName(String name) {
        this.name = name;
    }

    public void setCreated(java.util.Date created) {
        this.created = created;
    }

    public void setOwnerId(String ownerId) {
        this.ownerId = ownerId;
    }

    public void setObjectId(String objectId) {
        this.objectId = objectId;
    }

    public void setColour(String colour) {
        this.colour = colour;
    }

    public void setUpdated(java.util.Date updated) {
        this.updated = updated;
    }

    // Backendless' methods

    public Car save() {
        return Backendless.Data.of(Car.class).save(this);
    }

    public Long remove() {
        return Backendless.Data.of(Car.class).remove(this);
    }

    public static Car findById(String id) {
        return Backendless.Data.of(Car.class).findById(id);
    }

    public static Car findFirst() {
        return Backendless.Data.of(Car.class).findFirst();
    }

    public static Car findLast() {
        return Backendless.Data.of(Car.class).findLast();
    }
}

Here is the JAVA class for the Person table

package com.example.service;

import com.backendless.Backendless;
import com.backendless.persistence.BackendlessDataQuery;
import com..models.custom.Constants;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

public class Person {

    private String name;
    private String surname;
    private java.util.List<Car> cars;

    private String ownerId;
    private String objectId;
    private java.util.Date created;
    private java.util.Date updated;

    public Person() {
    }

    /**
     * The constructor gets a JSON formatted string and retrieve the information fron it in order to build the onbject.
     * The method is used both during the creation and the update.
     *
     * @param jsonString Contains JSON formatted data of the class.
     */
    public Person(String jsonString) {

        try {

            JSONTokener jsonTokener = new JSONTokener(jsonString);
            JSONObject person = (JSONObject) jsonTokener.nextValue();
            DateFormat dateFormat = new SimpleDateFormat(Constants.DATE_FORMAT);

            if (person.has("name"))
                this.name = person.getString("name");
            if (person.has("surname"))
                this.surname = person.getString("surname");
            if (person.has("idCars") && person.getJSONArray("idCars").length() > 0) {

                JSONArray jsonArray = person.getJSONArray("idCars");
                StringBuilder whereClause = new StringBuilder();

                whereClause.append("objectId='").append(jsonArray.getString(0)).append("'");
                for (int i = 1; i < jsonArray.length(); i++)
                    whereClause.append(" OR ").append("objectId='").append(jsonArray.getString(i)).append("'");

                BackendlessDataQuery dataQuery = new BackendlessDataQuery();
                dataQuery.setWhereClause(whereClause.toString());

                // Running "query" on Backendless to retrieve the cars to add
                List&lt;Car&gt; cars = Backendless.Persistence.of(Car.class).find(dataQuery).getData();

                // Adding new cars
                this.cars = cars;
            }

            if (person.has("ownerId"))
                this.ownerId = person.getString("ownerId");
            if (person.has("objectId"))
                this.objectId = person.getString("objectId");
            if (person.has("created"))
                this.created = dateFormat.parse(person.getString("created"));
            if (person.has("updated"))
                this.updated = dateFormat.parse(person.getString("updated"));

        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

    public void addCar(Car car) {
        if(this.cars == null)
            this.cars = new ArrayList&lt;Car&gt;();

        this.cars.add(car);
    }

    // Getters

    public java.util.Date getCreated() {
        return this.created;
    }

    public String getOwnerId() {
        return this.ownerId;
    }

    public String getSurname() {
        return this.surname;
    }

    public java.util.List&lt;Car&gt; getCars() {
        return this.cars;
    }

    public java.util.Date getUpdated() {
        return this.updated;
    }

    public String getName() {
        return this.name;
    }

    public String getObjectId() {
        return this.objectId;
    }

    // Setters

    public void setCreated(java.util.Date created) {
        this.created = created;
    }

    public void setOwnerId(String ownerId) {
        this.ownerId = ownerId;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public void setCars(java.util.List&lt;Car&gt; cars) {
        this.cars = cars;
    }

    public void setUpdated(java.util.Date updated) {
        this.updated = updated;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setObjectId(String objectId) {
        this.objectId = objectId;
    }

    // Backendless' methods

    public Person save() {
        return Backendless.Data.of(Person.class).save(this);
    }

    public Long remove() {
        return Backendless.Data.of(Person.class).remove(this);
    }

    public static Person findById(String id) {
        return Backendless.Data.of(Person.class).findById(id);
    }

    public static Person findFirst() {
        return Backendless.Data.of(Person.class).findFirst();
    }

    public static Person findLast() {
        return Backendless.Data.of(Person.class).findLast();
    }


Here is the custom server code class, called Service:

package com.example.service;


import com.backendless.Backendless;
import com.backendless.exceptions.BackendlessException;
import com.backendless.servercode.IBackendlessService;
import org.json.JSONObject;

public class Service implements IBackendlessService {

    public String insertPerson(String jsonData) {

        JSONObject result = new JSONObject();
        Boolean resultValue = false;

        try {

            Person person = new Person(jsonData);

            Person personToInsert = person.save();
            if (personToInsert != null)
                resultValue = true;
        } catch (BackendlessException e) {
            e.printStackTrace();
        }

        result.put("success", resultValue);
        return result.toString();
    }

    public String updatePerson(String jsonData) {

        JSONObject result = new JSONObject();
        Boolean resultValue = false;

        try {
            Person personToUpdate = new Person(jsonData);
            Person updatedPerson = Backendless.Persistence.save(personToUpdate);
            if (updatedPerson != null)
                resultValue = true;
        } catch (BackendlessException e) {
            e.printStackTrace();
        }

        result.put("success", resultValue);
        return result.toString();
    }
}

I have the main problme in the updatePerson method.

I hope you can find it useful

Regards,

starscream

Thank you for the code.
In order to work as expected - I mean replacing related collection with a new one - you should first retrieve the parent object with relations from server. This way client would ‘know’ that you’d like to work with relations also, not just with the parent object, otherwise it would add objects to collection, without unlinking the previous ones.

Hi Alexandr,

Thank you for your suggestion.

When you wrote: “[…] you should first retrieve the parent object with relations from server.”

    Do you mean that I have to retrieve my object first from the server, by setting also the depth of the query in order to retrieve all the relations?
I've tried something different on my custom server code class, and I also added an "update" method to my models classes in order to update only what is needed.

Here is the Person class

package com.example.models;
 
// Imports 
 
public class Person {
 
 // Attributes, Constructors and Methods (the same as above) 
 public Person update(String jsonString) {
 JSONObject person = getJSONObjectFromString(jsonString);
 try {
 if (person.has("name"))
 this.name = person.getString("name");
 if (person.has("surname"))
 this.surname = person.getString("surname");
 if (person.has("idCars") && person.getJSONArray("idCars").length() > 0) {
 JSONArray jsonArray = person.getJSONArray("idCars");
 StringBuilder whereClause = new StringBuilder();
 whereClause.append("objectId='").append(jsonArray.getString(0)).append("'");
 for (int i = 1; i < jsonArray.length(); i++)
 whereClause.append(" OR ").append("objectId='").append(jsonArray.getString(i)).append("'");
 BackendlessDataQuery dataQuery = new BackendlessDataQuery();
 dataQuery.setWhereClause(whereClause.toString());
 List&lt;Car&gt; carList = Backendless.Persistence.of(Car.class).find(dataQuery).getData();
 if (carList != null) {
 if (this.cars != null) {
 this.cars.clear();
 } else {
 this.cars = new ArrayList&lt;&gt;();
 }
 this.cars.addAll(carList);
 }
 }
 } catch (BackendlessException e) {
 e.printStackTrace();
 }
 return this.save();
 }
 /**************************************************************************
 * PRIVATE METHODS *
 **************************************************************************/
 private JSONObject getJSONObjectFromString(String jsonString) {
 JSONObject jsonObject = null;
 try {
 JSONTokener jsonTokener = new JSONTokener(jsonString);
 jsonObject = (JSONObject) jsonTokener.nextValue();
 } catch (JSONException e) {
 e.printStackTrace();
 }
 return jsonObject;
 }
}

Here is the Car class

package com.example.models;
 
// Imports 
 
public class Car {
 // Attributes, Constructors and Methods (the same as above)
 public Car update(String jsonString) {
 JSONObject person = getJSONObjectFromString(jsonString);
 try {
 if (person.has("name"))
 this.name = person.getString("name");
 if (person.has("colour"))
 this.colour = person.getString("colour");
 } catch (BackendlessException e) {
 e.printStackTrace();
 }
 return this.save();
 }
 /******************************************************************
 * PRIVATE METHODS *
 *******************************************************************/
 private JSONObject getJSONObjectFromString(String jsonString) {
 JSONObject jsonObject = null;
 try {
 JSONTokener jsonTokener = new JSONTokener(jsonString);
 jsonObject = (JSONObject) jsonTokener.nextValue();
 } catch (JSONException e) {
 e.printStackTrace();
 }
 return jsonObject;
 }
}

And finaly here it is my custom server code used to both insert and update the records on the DB

package com.example.service;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.json.JSONException;
import com.example.models.Car;
import com.example.models.Person;
import com.backendless.servercode.IBackendlessService;
import com.backendless.exceptions.BackendlessException;
public class Services implements IBackendlessService {
 /************************************************************
 * INSERT *
 *************************************************************/
 public String insertPerson(String jsonData) {
 JSONObject result = new JSONObject();
 Boolean resultValue = false;
 try {
 Person person = new Person(jsonData);
 Person personToInsert = person.save();
 if (personToInsert != null)
 resultValue = true;
 } catch (BackendlessException e) {
 e.printStackTrace();
 }
 result.put("success", resultValue);
 return result.toString();
 }
 public String insertCar(String jsonData) {
 JSONObject result = new JSONObject();
 Boolean resultValue = false;
 try {
 Car car = new Car(jsonData);
 Car carToInsert = car.save();
 if (car != null)
 resultValue = true;
 } catch (BackendlessException e) {
 e.printStackTrace();
 }
 result.put("success", resultValue);
 return result.toString();
 }
 /*************************************************************
 * UPDATE *
 **************************************************************/
 public String updatePerson(String jsonData) {
 JSONObject result = new JSONObject();
 Boolean resultStatus = false;
 try {
 JSONObject jsonnedData = getJSONObjectFromString(jsonData);
 if (jsonnedData != null && jsonnedData.has("objectId")) {
 Person personToUpdate = Person.findById(jsonnedData.getString("objectId"));
 if (personToUpdate != null) {
 Person updatedPerson = personToUpdate.update(jsonData);
 if (updatedPerson != null) {
 resultStatus = true;
 }
 }
 }
 } catch (BackendlessException e) {
 e.printStackTrace();
 resultStatus = false;
 JSONArray errorData = getBackendlessExceptionErrorJSONArray(e);
 result.put("error", errorData);
 }
 result.put("success", resultStatus);
 return result.toString();
 }
 public String updateCar(String jsonData) {
 JSONObject result = new JSONObject();
 Boolean resultStatus = false;
 try {
 JSONObject jsonnedData = getJSONObjectFromString(jsonData);
 if (jsonnedData != null && jsonnedData.has("objectId")) {
 Car carToUpdate = Car.findById(jsonnedData.getString("objectId"));
 if (carToUpdate != null) {
 Car updatedCar = carToUpdate.update(jsonData);
 if (updatedCar != null) {
 resultStatus = true;
 }
 }
 }
 } catch (BackendlessException e) {
 e.printStackTrace();
 resultStatus = false;
 JSONArray errorData = getBackendlessExceptionErrorJSONArray(e);
 result.put("error", errorData);
 }
 result.put("success", resultStatus);
 return result.toString();
 }
 /***************************************************************************************
 * PRIVATE METHODS *
 ***************************************************************************************/
 private JSONObject getJSONObjectFromString(String jsonString) {
 JSONObject jsonObject = null;
 try {
 JSONTokener jsonTokener = new JSONTokener(jsonString);
 jsonObject = (JSONObject) jsonTokener.nextValue();
 } catch (JSONException e) {
 e.printStackTrace();
 }
 return jsonObject;
 }
 private JSONArray getBackendlessExceptionErrorJSONArray (BackendlessException e) {
 JSONArray errorData = new JSONArray();
 JSONObject data = new JSONObject();
 data.put("code", e.getCode());
 data.put("message", e.getMessage());
 data.put("detail", e.getDetail());
 return errorData;
 }
}

I am really sorry about this long comment, but I think it is necessary in order to better understand what is going on.

    Do you think that there is a better approach for retrieving the data from the DB before updating it? I really don't like wasting and API call in the updating methods of my custom server code, do you know a better solution which avoids wasting API calls?

Thank you for you for your help and answer.

PS: the code area is really bad in the comments, it doen’t keep the indentetions, and I can’t either attach the Java classes. I’m sorry

Ok, I guess that I’ve got your idea and here is my answer.
There are only two ways of updating object if you have it’s objectId:

  1. Without pre-retrieving it from base:
String objectId = 'qwerty';
Foo obj = new Foo();
obj.setObjectId(objectId);
// ... setting other properties ...
Backendless.Data.of(Foo.class).save(obj);

But this approach wouldn’t work well if the object has relations. As you see in your case, related objects would be added, but the old ones would stay.
2. Another approach includes retrieving object from base with it’s related objects.

String objectId = 'qwerty';
Foo obj = Backendless.Data.of(Foo.class).findById(objectId, 1);
// ... updating object ...
Backendless.Data.of(Foo.class).save(obj);

This approach would work fine with relations.
So, I guess that in your case you’re able to make this API call.
But if it’s made from BL code - then it wouldn’t be very time-expensive because code would be executed on the same instance with database.

Yes, you got what I meant, thank you Alexandr for your help!!!
Very nice comment the last one and really helpful!

Regards,
Starscream