Страницы

Поиск по вопросам

пятница, 24 января 2020 г.

Android Room - сохранение зависимостей

#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 List employees;

}


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 List selectAllCompanies(); @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

Комментариев нет:

Отправить комментарий