#java #android #android_room #room
Пытаюсь приспособить 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 Listemployees; } EmployeeDao.java для сохранения @Dao public abstract class EmployeeDao { @Query("SELECT * FROM companies") public abstract List selectAllCompanies(); @Query("SELECT * FROM companies WHERE name LIKE :companyName") public abstract List getEmployeesByCompanyName(String companyName); @Transaction public void insert(String companyName, List employeeNames) { // Prepare employee entities List 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]. Пока мне видятся следующие варианты: Самому генерировать PK(возникает проблема с генерацией уникального ключа и его размера) для CompanyEntity и вручную сохранять его в EmployeeEntity В Dao сначала сохранять CompanyEntity, прочитать его и использовать PK, который для нас сгенероровал SQLite при добавлении записи в таблицу, при добавлении связанных EmployeeEntity (не нравится - коряво) Собственно главный вопрос: Как в 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
Ответы
Ответ 1
Английский 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 ListselectAllCompanies(); @Transaction @Query("SELECT * FROM companies WHERE name LIKE :companyName") public abstract List getEmployeesByCompanyName(String companyName); @Transaction public void insert(CompanyEntity companyEntity, List 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 Ссылки https://developer.android.com/training/data-storage/room/accessing-data https://www.sqlite.org/autoinc.html
Комментариев нет:
Отправить комментарий