Room Database TypeConverter 未在某些集合中使用,例如字符串集

Posted

技术标签:

【中文标题】Room Database TypeConverter 未在某些集合中使用,例如字符串集【英文标题】:Room Database TypeConverter is not used in some collections e.g. Set of String 【发布时间】:2021-05-20 14:35:23 【问题描述】:

所以我有这个数据对象,它有一组字符串(代表不可索引的关键字,但在这里无关紧要)作为成员,当这个集合为空时 Room 表现不佳:

@Parcelize
@Entity(tableName = TABLE_NAME)
data class DataItem(
        @PrimaryKey
        @ColumnInfo(name = COLUMN_ID, index = true) val id: Long,
        @ColumnInfo(name = COLUMN_GROUP, index = true) var group: Int,
        @ColumnInfo(name = COLUMN_TEXT, index = true) var text: String,
        @ColumnInfo(name = COLUMN_STRING_SET) var stringSet: Set<String>
): Parcelable 

    companion object
    
        const val TABLE_NAME = "TestDataItems"
        const val COLUMN_ID = "id"
        const val COLUMN_GROUP = "groupColumn"
        const val COLUMN_TEXT = "text"
        const val COLUMN_STRING_SET = "stringSet"
    

首先,我收到一个错误,因为房间不知道如何处理这些数据。很公平,让我们实现一个类型转换器。在没有 JSON 转换的情况下保持简单,因为我知道 \n 将是一个有效的分隔符。

class TestTypeConverter 

    @TypeConverter
    fun fromStringSet(value: Set<String>): String 
        return value.joinToString ( "\n" )
    

    @TypeConverter
    fun toStringSet(value: String): Set<String> 
        return value.split("\n").toSet()
    

Room 接受了我谦虚的类型转换提议,并从我定义的接口编译了一个不错的 dao 实现。

@androidx.room.Dao
@TypeConverters(value = [TestTypeConverter::class])
interface TestDao

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertItem(item: DataItem)

    @Query("SELECT * FROM $TABLE_NAME")
    fun getAllItems() : LiveData<List<DataItem?>>

    @Update
    fun updateItem(item: DataItem)

    @Delete
    fun deleteItem(item: DataItem)

    @Query("UPDATE $TABLE_NAME SET $COLUMN_STRING_SET= :stringSet WHERE $COLUMN_ID= :id")
    fun updateStringSet(id: Long, stringSet: Set<String>)

在测试过程中,我确实发现引擎盖下存在问题。在更新适配器中比较两种不同的类型转换实现(它将完整的数据对象作为输入,在仅针对更新字符串集的自定义查询中比较一次:

this.__updateAdapterOfDataItem = new EntityDeletionOrUpdateAdapter<DataItem>(__db) 
      @Override
      public String createQuery() 
        return "UPDATE OR ABORT `TestDataItems` SET `id` = ?,`groupColumn` = ?,`text` = ?,`stringSet` = ? WHERE `id` = ?";
      

      @Override
      public void bind(SupportSQLiteStatement stmt, DataItem value) 
        stmt.bindLong(1, value.getId());
        stmt.bindLong(2, value.getGroup());
        if (value.getText() == null) 
          stmt.bindNull(3);
         else 
          stmt.bindString(3, value.getText());
        
        final String _tmp;
        _tmp = __testTypeConverter.fromStringSet(value.getStringSet()); // <-- Room uses my type converter
        if (_tmp == null) 
          stmt.bindNull(4);
         else 
          stmt.bindString(4, _tmp);
        
        stmt.bindLong(5, value.getId());
      
    ;
  


@Override
  public void updateStringSet(final long id, final Set<String> stringSet) 
    __db.assertNotSuspendingTransaction();
    StringBuilder _stringBuilder = StringUtil.newStringBuilder();
    _stringBuilder.append("UPDATE TestDataItems SET stringSet= ");
    final int _inputSize = stringSet.size();
    StringUtil.appendPlaceholders(_stringBuilder, _inputSize);
    _stringBuilder.append(" WHERE id= ");
    _stringBuilder.append("?");
    final String _sql = _stringBuilder.toString();
    final SupportSQLiteStatement _stmt = __db.compileStatement(_sql);
    int _argIndex = 1;
    for (String _item : stringSet)                            // <-- Room IGNORES the type converter
      if (_item == null) 
        _stmt.bindNull(_argIndex);
       else 
        _stmt.bindString(_argIndex, _item);
      
      _argIndex ++;
    
    _argIndex = 1 + _inputSize;
    _stmt.bindLong(_argIndex, id);
    __db.beginTransaction();
    try 
      _stmt.executeUpdateDelete();
      __db.setTransactionSuccessful();
     finally 
      __db.endTransaction();
    
  

所以在自定义查询中room 不使用提供的类型转换器,而是将集合视为一个列表,并希望添加以逗号分隔的字符串值。这会导致一些明显的问题:

我的类型转换器使用了不同的分隔符,所以查询不兼容! 如果集合为空,则 SQL 字符串无效

两者都会导致运行时故障,而防止这些故障应该是整个空间的重点!

所以问题是:我做错了吗?我将其作为错误报告给 Google,但尚未收到任何回复。

可能相关的问题here 和here 和here

【问题讨论】:

我转换它没关系(我都试过了)。实际上,使用 JSON 转换器最终仍会作为 JSON 字符串存储在数据库中。关键是 Room 会忽略 TypeConverter,不管里面有什么。 【参考方案1】:

只需使用String:

@Query("UPDATE $TABLE_NAME SET $COLUMN_STRING_SET= :string WHERE $COLUMN_ID= :id")
fun updateStringSet(id: Long, string: String)

【讨论】:

当然,这在这种情况下可行,但这只是一种解决方法。不得不做这样的事情有点令人失望,我看起来确实应该开箱即用。

以上是关于Room Database TypeConverter 未在某些集合中使用,例如字符串集的主要内容,如果未能解决你的问题,请参考以下文章

获取 android 房间数据库 java.lang.IllegalArgumentException 的以下异常:@androidx.room.Database 未定义元素视图()

Room Database Migration 无法正确处理 ALTER TABLE 迁移

添加1到多关系android Room Database?

Android Room Database:如何嵌入多个实体

Android Room Database 执行批量更新的正确语法是啥?

Room Database TypeConverter 未在某些集合中使用,例如字符串集