【问题标题】:Duplicate data entry on update in Room Database房间数据库中更新的重复数据条目
【发布时间】:2021-01-08 06:01:38
【问题描述】:

我有一个笔记应用程序。用户可以创建新笔记或更改现有笔记。

在此应用程序中,我试图在用户更改现有笔记后立即自动保存笔记。因此,我使用textWatcher 并在文本观察者的onTextChanged() 方法中调用saveNotes() 方法。因此,我打算在每次用户从文本中添加或删除任何字符时保存注释。

var ide: Int? = null  //Global Variable
//in OnCreate()
if(intent.getBooleanExtra("isViewOrUpdate", false)){
            viewOrOpenedNote = intent.getSerializableExtra("note") as Note?
            ide = viewOrOpenedNote?.id

在上面的两个代码示例中.. 整个想法是让变量ide 充当检查器,即如果用户正在创建新笔记,它将返回 null,否则,它将存储笔记的 id 如果用户有在现有笔记上打开以查看或编辑笔记。

下面是textWatcher

title_edit_text.addTextChangedListener((object : TextWatcher {
            override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
            override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                saveNote()   // here I 
            }
            override fun afterTextChanged(p0: Editable?) {}
        }))

saveNote()

private fun saveNote() {
                val note = Note()
                note.setTitle(Title)

                if (ide != null) {
                    note.setId(ide!!)  // Here I am assigning the note id to `ide` in case the 
                                         `saveNote()` is called when user has opened an existing note. If 
                                          this is a new note then ide will be null automatically.
                }
                class SaveNotes : AsyncTask<Void, Void, Void>() {
                    override fun doInBackground(vararg void: Void?): Void? {
                        NotesDatabase.getDatabase(applicationContext).noteDao().insertNote(note)
                        return null
                    }

                    override fun onPostExecute(result: Void?) {
                        super.onPostExecute(result)
                        ide = note.id               //This is because: If user has added a first 
                                                     character in new note, then after that an id will be 
                                                     assigned to this new database, whenever 
                                                    `saveNote()` method is called again after text change 
                                                     I need to save the changed note to the same existing 
                                                     id. I hope I am clear.

                     // ***UPDATE*** After much contemplation I have found out 
                       that here lies the problem! changing the value of ide by 
                       simply `ide = note.id` will always give null value for new 
                       note, as this id is not received from the created note, 
                       but from the blank note instance I created above. 
                    }
                }
                SaveNotes().execute()
}

问题:即使采取了上述预防措施,每次我更改笔记时,都会创建相同笔记的多个实例。示例:

假设我输入:你好

我得到的结果是 5 个不同 id 的不同笔记。及文字如下:

h //id = 5

他 //id = 4

hel //id = 3

地狱 //id = 2

你好 //id = 1

NoteDao.java

@Dao
public interface NoteDao {

    @Query("SELECT * FROM note ORDER BY id DESC")
    List<Note> getAllNotes();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertNote(Note note);

    @Delete
    void delete(Note note);
}

Note.java

@Entity(tableName = "note")
public class Note implements Serializable {

    @PrimaryKey(autoGenerate = true)
    private int id;

    @ColumnInfo(name = "title")

NoteDatabase.java

 public static synchronized NotesDatabase getDatabase(Context context)
    {
        if(notesDatabase == null){
           notesDatabase =  Room.databaseBuilder(context
            , NotesDatabase.class,
                    "note_db"
                    )
                   .addMigrations(MIGRATION_1_2)
                   .build();
        }
        return notesDatabase;
    }


    public abstract NoteDao noteDao();

【问题讨论】:

  • 因为您每次在 onTextChanged 内部调用方法时都会插入注释。
  • 你应该使用 update 方法并用它在数据库中的 id 更新笔记。
  • 但我使用的是冲突策略对吗?
  • @ShahzadAfridi 即@Insert(onConflict = OnConflictStrategy.REPLACE)
  • 检查下面的答案。

标签: java android database kotlin android-room


【解决方案1】:

每次在 onTextChange 中调用 saveNote 时,您都会创建一个新实例 (viewOrOpenedNote),因此请传递您从数据库中获取或当前显示给用户的旧实例注释对象。

//Pass the same note instance which you are showing on the screen.
@Update
fun updateNote(note: Note)

//Give note the same id as It saved in the previous note. e-g create a note instance but set Id = viewOrOpenedNote.id so that is where Conflict will arise and the database will replace it.
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertNote(note: Note) : Long

使用协程

第一步:

def coroutines_android_version = '1.3.9'
// Coroutines libraries
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_core"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version"

第二步:

在班级级别上清除它。

private val ioScope = CoroutineScope(Dispatchers.IO)

第三步:

ioScope.launch {
    saveNote()
}

suspend fun saveNote(){
    NotesDatabase.getDatabase(applicationContext).noteDao().insertNote(note)
}

【讨论】:

  • 但是你必须看到我是用同一个Id来插入的。我的冲突策略是替换,所以当保存两个具有相同 id 的不同笔记数据时,它应该替换为最新的一个
  • viewOrOpenedNote 的意图在 onCreate 方法中,所以它应该只工作一次
  • 你应该采用的一种方法让我把它写下来。
  • 还有一件事为什么使用异步任务,它已被弃用。您可以通过使用协程以最少的代码行来实现这一点。如果你想让我写下来,我可以帮你写。
  • 您的主键是自动生成的,因此您的设置 od id 将不起作用,并且每次它都会使用新 id 存储一条记录。您最好使用您的 id 更新该记录
【解决方案2】:

像这样在你的 Dao 中创建一个函数

@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(note: Note): Int

并将以下代码放入您的 saveNote 函数中

fun saveNote(item: Note) {
    database.runInTransaction {
        val id = item.id

        if(id == null)
            getNoteDao().insert(item)
        else
            getNoteDao().update(item)
    }
}

【讨论】:

  • 我的代码在您的第三行抛出错误。什么是 getId(),我应该把它放在哪里?
  • 它无法识别该方法
  • 它可能是一种返回笔记ID的新方法
  • 谢谢@rcs 但我应该用什么来注释它?你能写出那个方法吗?
  • 据我所知更新查询,我们不需要onConflict 进行更新
【解决方案3】:

总结一下

考虑到您所做的更改,在您的 postExecuteMethod() 中指出 ide = note.id 处的错误。执行以下操作:

1.NoteDao.java 中更改插入块

@Insert(onConflict = OnConflictStrategy.REPLACE)
    long insertNote(Note note);  //long istead of void if you need the id as return

2.ide 的数据类型从 int

更改为 Long
    var ide: Long? = null

3.onCreate()

if(intent.getBooleanExtra("isViewOrUpdate", false)){
            viewOrOpenedNote = intent.getSerializableExtra("note") as Note?
              ide = viewOrOpenedNote?.id?.toLong() //Convert to long

4. 您现在只需将saveNote() 方法替换为下面的这个块。

private fun saveNote() {
                val database = NotesDatabase.getDatabase(applicationContext) //add this here
                val note = Note()
                note.setTitle(Title)

                if (ide != null) {
                   note.setId(ide!!.toInt())  //convert to int as ide is long
                }
                class SaveNotes : AsyncTask<Void, Void, Void>() {
                    override fun doInBackground(vararg void: Void?): Void? {
                            ide = database.noteDao().insertNote(note)  // two things inone line: first insert the note and then get the id of the note saved
                        return null
                    }

                    override fun onPostExecute(result: Void?) {
                        super.onPostExecute(result)
                        // Let this be empty
                    }
                }
                SaveNotes().execute()
}

【讨论】:

    猜你喜欢
    • 2023-02-20
    • 1970-01-01
    • 2020-04-14
    • 2019-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-12
    相关资源
    最近更新 更多