Пытаюсь приспособить Room для работы с зависимостями "один-ко-многим". Про то, как использовать @Relation для чтения записей рассказывается в [1,2,3,4]. А вот про сохранение зависимостей через @Relation информации совсем мало - в [3] внешние ключи явно задаются при создании объектов, в [4] упоминается только то, что сохранение(вставка) должно идти в одной транзакции. По аналогии с [2] пробовал так:
CompanyEntity.java
@Entity(tableName = "companies", indices = @Index(value = "name"))
public class CompanyEntity {
@PrimaryKey (autoGenerate = true)
public int id;
@NonNull
public final String name;
public CompanyEntity(@NonNull String name) {
this.name = name;
}
}
EmployeeEntity.java
@Entity(tableName = "employee_list",
foreignKeys = @ForeignKey(
entity = CompanyEntity.class,
parentColumns = "id",
childColumns = "company_id",
onDelete = CASCADE),
indices = @Index("company_id"))
public class EmployeeEntity {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "company_id")
// If int is used instead Integer
// android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)
// will be thrown
public Integer companyId;
@NonNull
public final String name;
public EmployeeEntity(@NonNull String name) {
this.name = name;
}
}
Cледующий "класс зависимости"
public class CompanyEmployees {
@Embedded
public CompanyEntity company;
@Relation (parentColumn = "id", entityColumn = "company_id", entity = EmployeeEntity.class)
public List<EmployeeEntity> employees;
}
EmployeeDao.java для сохранения
@Dao
public abstract class EmployeeDao {
@Query("SELECT * FROM companies")
public abstract List<CompanyEntity> selectAllCompanies();
@Query("SELECT * FROM companies WHERE name LIKE :companyName")
public abstract List<CompanyEmployees> getEmployeesByCompanyName(String companyName);
@Transaction
public void insert(String companyName, List<String> employeeNames) {
// Prepare employee entities
List<EmployeeEntity> employeeEntities = new ArrayList<>(employeeNames.size());
for (String employeeName : employeeNames) {
employeeEntities.add(new EmployeeEntity(employeeName));
}
// Create "relation" object and set-up fields
CompanyEmployees companyEmployees = new CompanyEmployees();
companyEmployees.company = new CompanyEntity(companyName);
companyEmployees.employees = employeeEntities;
// Insert "relation" object
insert(companyEmployees.company);
for (EmployeeEntity employeeEntity : companyEmployees.employees) {
insert(employeeEntity);
}
}
@Insert
public abstract void insert(CompanyEntity company);
@Insert
public abstract void insert(EmployeeEntity employee);
}
Но безуспешно:
Если в EmployeeEntity использовать примитивный тип int для company_id, при попытке вставить данные, выбрасыавется "android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787)"
При замене на Integer, данные успешно вставляются в таблицу, но для всех записей в таблице employee_list столбец company_id равен NULL (очевидно что @Query запрос при этом будет бесполезен)
При дальнейшем поиске нашел похожий вопрос на английском so[5] - насколько я понял из обсуждения, сохранять @Relation Room не умеет[6].
Пока мне видятся следующие варианты:
Собственно главный вопрос:
Как в Room "нормально" сохранить зависимость @Relation?
Зачем нужен @Relation (если действительно сохранение не поддерживается), если можно SQL JOIN? Для удобствы (не возиться с JOIN)?
Список источников:
https://developer.android.com/reference/android/arch/persistence/room/Relation
https://medium.com/@magdamiu/android-room-persistence-library-relations-75bbe02e8522
https://android.jlelse.eu/android-architecture-components-room-relationships-bf473510c14a
https://habr.com/post/349280/
https://stackoverflow.com/questions/44667160/android-room-insert-relation-entities-using-room
https://issuetracker.google.com/issues/62848977
Ссылка на тестовый проект http://rgho.st/6ryvqvZ5f
Английский SO молчит, поэтому предлагаю следующее решение.
В документации сказано, что @Insert метод может возвращать long, который является rowId вставленного элемента[1].
If the @Insert method receives only 1 parameter, it can return a long, which is the new rowId for the inserted item. If the parameter is an array or a collection, it should return long[] or List instead.
В документации SQLite[2] говориться, что если таблица содержит столбец типа INTEGER PRIMARY KEY, тогда этот столбец становится псевдонимом(alias) для ROWID :
If a table contains a column of type INTEGER PRIMARY KEY, then that column becomes an alias for the ROWID. You can then access the ROWID using any of four different names, the original three names described above or the name given to the INTEGER PRIMARY KEY column. All these names are aliases for one another and work equally well in any context.
Из вышесказанного, я делаю вывод, что INTEGER PRIMARY KEY (у меня он еще и AUTOINCREMENT) вставленной в таблицу записи, и rowId возвращенный, при вставке этой записи должны совпадать. И можно сделать следующее:
1) CompanyEntity.java
@Entity(tableName = "companies", indices = @Index(value = "name", unique = true)) public class CompanyEntity {
@PrimaryKey (autoGenerate = true)
public int id;
@NonNull
@ColumnInfo(name = "name")
private final String mCompanyName;
public CompanyEntity(@NonNull String companyName) {
mCompanyName = companyName;
}
@NonNull
public String getCompanyName() {
return mCompanyName;
}
}
2) EmployeeEntity.java
@Entity(tableName = "employee_list",
foreignKeys = @ForeignKey(
entity = CompanyEntity.class,
parentColumns = "id",
childColumns = "company_id",
onDelete = CASCADE),
indices = @Index("company_id"))
public class EmployeeEntity {
@PrimaryKey(autoGenerate = true)
public int id;
@ColumnInfo(name = "company_id")
private long mCompanyId;
@NonNull
@ColumnInfo(name = "name")
private final String mName;
public EmployeeEntity(@NonNull String name) {
mName = name;
}
@NonNull
public String getName() {
return mName;
}
public long getCompanyId() {
return mCompanyId;
}
public void setCompanyId(long companyId) {
mCompanyId = companyId;
}
}
3) EmployeeDao.java
@Dao
public abstract class EmployeeDao {
@Query("SELECT * FROM companies")
public abstract List<CompanyEntity> selectAllCompanies();
@Transaction
@Query("SELECT * FROM companies WHERE name LIKE :companyName")
public abstract List<CompanyEmployees> getEmployeesByCompanyName(String companyName);
@Transaction
public void insert(CompanyEntity companyEntity, List<EmployeeEntity> employeeEntities) {
// Save rowId of inserted CompanyEntity as companyId
final long companyId = insert(companyEntity);
// Set companyId for all related employeeEntities
for (EmployeeEntity employeeEntity : employeeEntities) {
employeeEntity.setCompanyId(companyId);
insert(employeeEntity);
}
}
// If the @Insert method receives only 1 parameter, it can return a long,
// which is the new rowId for the inserted item.
// https://developer.android.com/training/data-storage/room/accessing-data
@Insert(onConflict = REPLACE)
public abstract long insert(CompanyEntity company);
@Insert
public abstract void insert(EmployeeEntity employee);
}
У меня этот вариант работает
Полный исходный код тестового проекта https://github.com/relativizt/android-room-one-to-many-auto-pk
Ссылки
Виртуальный выделенный сервер (VDS) становится отличным выбором
Пытаюсь освоить SpringДелаю все по видео Spring Потрошитель, но на экран ничего не выходит
как ??? В игре Добавить кнопку перезагрузки после проигрыша, чтобы кнопка появлялись в JPanel после проигрыша и при нажатии на неё приложение...
Есть приложение которое работает с wordpress, контент открывается через webviewИ вот когда я нажимаю на ссылку в webview она открывается во внешнем браузере,...
В учебных целях делаю приложение которое собирает строку (банковский счёт) из разных значений согласно этой таблице первые 3 символа счёта...