#android #android-room
#Android #android-комната
Вопрос:
Я довольно новичок в Android Room и SQLite в целом, поэтому извините, если это простой вопрос.
Я получаю данные из API, которые я хотел бы вставить в базу данных, чтобы они были доступны, когда устройство находится в автономном режиме.
В зависимости от конечной точки API некоторые поля моих объектов данных могут быть нулевыми (подумайте о сводке только с основными полями по сравнению с полностью подробным объектом со всеми полями)
Чтобы сохранить базу данных в чистоте, я хотел бы обновить записи, но только те столбцы, которые не являются нулевыми (например, для которых у меня есть новые значения), а остальные столбцы оставить нетронутыми.
Вот несколько примеров классов для пояснения:
Человек
@Entity(tableName = "person", indices = {
@Index(value = "id", unique = true)
})
public class Person {
@PrimaryKey
public int id;
public String name;
public String description;
}
Пример:
// create db
RoomDB db = RoomDB.create(ctx);
// create some sample objects
final Person p2 = new Person(2, "Peter", null);
// insert them into the db
db.personDao().insert(p2);
// create a updated peter that likes spiders
// but has no name (as a example)
final Person newPeter = new Person(2, null, "Peter likes spiders");
// and update him
db.personDao().updateNonNull(newPeter);
// now we read him back
final Person peter = db.personDao().getById(2);
В этом примере желаемые значения ‘peter’ будут:
id = 2
name = "Peter"
description = "Peter likes spiders"
Однако, используя @Update или @Insert в комнате, я могу получить только это:
id = 2
name = null
description = "Peter likes spiders"
Единственный способ, который я нашел для достижения этой цели, — вручную получить объект и дополнить значения следующим образом:
@Transaction
public void updateNonNull(Person newPerson) {
final Person oldPerson = getById(newPerson.id);
if (oldPerson == null) {
insert(newPerson);
return;
}
if (newPerson.name == null)
newPerson.name = oldPerson.name;
if (newPerson.description == null)
newPerson.description = oldPerson.description;
update(newPerson);
}
Однако это привело бы к довольно большому количеству кода с более крупными объектами…
Итак, мой вопрос, есть ли лучший способ сделать это?
Редактировать:
После некоторого тестирования с помощью SQL от @Priyansh Kedia я обнаружил, что эти функции действительно работают по назначению и делают это с более высокой производительностью, чем java.
Однако, поскольку для выполнения инструкции SQL мне потребовалось бы писать огромные запросы, я решил использовать решение, основанное на отражении, как показано ниже. Я сделал это только потому, что функция вызывается нерегулярно, поэтому более низкая производительность не будет иметь большого значения.
/**
* merge two objects fields using reflection.
* replaces null value fields in newObj with the value of that field in oldObj
* <p>
* assuming the following values:
* oldObj: {name: null, desc: "bar"}
* newObj: {name: "foo", desc: null}
* <p>
* results in the "sum" of both objects: {name: "foo", desc: "bar"}
*
* @param type the type of the two objects to merge
* @param oldObj the old object
* @param newObj the new object. after the function, this is the merged object
* @param <T> the type
* @implNote This function uses reflection, and thus is quite slow.
* The fastest way of doing this would be to use SQLs' ifnull or coalesce (about 35% faster), but that would involve manually writing a expression for EVERY field.
* That is a lot of extra code which i'm not willing to write...
* Besides, as long as this function isn't called too often, it doesn't really matter anyway
*/
public static <T> void merge(@NonNull Class<T> type, @NonNull T oldObj, @NonNull T newObj) {
// loop through each field that is accessible in the target type
for (Field f : type.getFields()) {
// get field modifiers
final int mod = f.getModifiers();
// check this field is not status and not final
if (!Modifier.isStatic(mod)
amp;amp; !Modifier.isFinal(mod)) {
// try to merge
// get values of both the old and new object
// if the new object has a null value, set the value of the new object to that of the old object
// otherwise, keep the new value
try {
final Object oldVal = f.get(oldObj);
final Object newVal = f.get(newObj);
if (newVal == null)
f.set(newObj, oldVal);
} catch (IllegalAccessException e) {
Log.e("Tenshi", "IllegalAccess in merge: " e.toString());
e.printStackTrace();
}
}
}
}
Ответ №1:
В room нет встроенного метода для этого
Что вы можете сделать, так это поставить галочку в запросе для вашего метода обновления.
@Query("UPDATE person SET name = (CASE WHEN :name IS NOT NULL THEN :name ELSE name END), description = (CASE WHEN :description IS NOT NULL THEN :description ELSE description END) WHERE id = :id")
Person update(id: Int, name: String, description: String)
Мы написали запрос на обновление для SQL, который проверяет, являются ли вставленные значения null или нет, и если они равны null, то сохраняются предыдущие значения.
Комментарии:
1. Было бы проще / лаконичнее в использовании
SET name = coalesce(:name,name)
…. илиifnull(:name,name)
, скорее, чемCASE WHEN .... THEN .... END
2. Привет, спасибо за этот совет, я не знал, что в SQL есть такая функция. Однако для этого потребовалось бы совсем немного кода, поэтому я решил использовать функцию, аналогичную моей первоначальной идее, но с использованием отражения.