Android Room - 如何获取自动生成id的新插入行的值

233

这是我使用Room Persistence Library将数据插入数据库的方法:

实体(Entity):

@Entity
class User {
    @PrimaryKey(autoGenerate = true)
    public int id;
    //...
}

数据访问对象:

@Dao
public interface UserDao{
    @Insert(onConflict = IGNORE)
    void insertUser(User user);
    //...
}

在不编写单独的查询语句的情况下,是否可能在上述方法完成插入后即返回用户id?


1
你有尝试过使用intlong替换void作为@Insert操作的返回结果吗? - MatPag
还没有。我会试一试! - SpiralDev
我已经添加了一个答案,因为我在文档中找到了参考内容,并且我非常有信心它会起作用 ;) - MatPag
4
这个可以用 "aSyncTask" 来完成吗?你是怎么从仓库函数中返回值的? - Nimitz14
8个回答

312

根据文档(代码片段下方)所述

一个带有@Insert注解的方法可以返回以下内容:

  • long(单次插入操作)
  • long[]Long[]List<Long>(多次插入操作)
  • void(如果您不关心插入的ID)

10
文档中为什么ID类型说是int,但是返回的却是long?这是因为假设ID永远不会太大而以long类型返回。所以行ID和自动生成的ID实际上是同一件事情吗? - Michael Vescovo
26
在SQLite中,你可以拥有的最大主键ID是一个64位带符号整数,因此最大值为9,223,372,036,854,775,807(只能是正数,因为它是一个ID)。在Java中,int是一个32位带符号整数,其最大正值为2,147,483,647,因此无法表示所有的ID。您需要使用Java long来表示所有的ID,其最大值为9,223,372,036,854,775,807。文档仅为示例,但API是根据这一点设计的(这就是为什么它返回long而不是int或double的原因)。 - MatPag
2
好的,所以它应该是一个long类型。但是也许在大多数情况下,在SQLite数据库中不会有90亿行,因此他们为userId使用int作为示例,因为它占用更少的内存(或者这是个错误)。这就是我从中得到的。感谢解释为什么返回long。 - Michael Vescovo
3
你说得对,但是Room的API即使在最坏的情况下也应该能够正常工作,并且必须遵循SQlite的规范。在这种特定情况下,使用int而不是long实际上是一样的,额外的内存消耗可以忽略不计。 - MatPag
2
请忽略我的上一条评论 - sqlite文档中说:“rowid表的主键(如果有)通常不是表的真正主键,因为它不是底层B-tree存储引擎使用的唯一键。例外情况是当rowid表声明一个INTEGER PRIMARY KEY时。在这种情况下,INTEGER PRIMARY KEY成为rowid的别名。”(https://www.sqlite.org/rowidtable.html) 因此,如果您有一个作为kotlin/java Long类型的主键,则它应始终与rowId相同,该rowId由Dao查询返回。 - Dave Kirby
显示剩余12条评论

41

@Insert 函数可以返回 void, long, long[]List<Long>。 请尝试使用此函数。

 @Insert(onConflict = OnConflictStrategy.REPLACE)
  long insert(User user);

 // Insert multiple items
 @Insert(onConflict = OnConflictStrategy.REPLACE)
  long[] insert(User... user);

6
返回一个Single,使用fromCallable()方法和Lambda表达式,该表达式调用dbService的YourDao()方法并插入mObject。 - murt
这里的Single是什么?如何导入? - Biswas Khayargoli

12
根据文档,被@Insert注释的函数可以返回rowId。
如果@Insert方法只接收一个参数,它可以返回long类型,这是插入项的新rowId。如果参数是一个数组或集合,那么它应该返回long[]或List<Long>。
我的问题是它返回rowId而不是id,我仍然没有找到如何使用rowId获取id的方法。
编辑:我现在知道如何从rowId获取id了。以下是SQL命令:
SELECT id FROM table_name WHERE rowid = :rowId

9
通过以下代码片段获取行ID。 它使用具有Future的ExecutorService上的可调用对象。
 private UserDao userDao;
 private ExecutorService executorService;

 public long insertUploadStatus(User user) {
    Callable<Long> insertCallable = () -> userDao.insert(user);
    long rowId = 0;

    Future<Long> future = executorService.submit(insertCallable);
     try {
         rowId = future.get();
    } catch (InterruptedException e1) {
        e1.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    return rowId;
 }

参考:Java Executor Service Tutorial,以获取有关 Callable 的更多信息。


1
调用 future.get() 会阻塞 UI 线程吗? - fahmy

7
在您的 Dao 中,插入查询返回一个 Long,即插入的行号。
 @Insert(onConflict = OnConflictStrategy.REPLACE)
 fun insert(recipes: CookingRecipes): Long

在你的模型(仓库)类中:(MVVM)
fun addRecipesData(cookingRecipes: CookingRecipes): Single<Long>? {
        return Single.fromCallable<Long> { recipesDao.insertManual(cookingRecipes) }
}

在您的ModelView类中:(MVVM)使用DisposableSingleObserver处理LiveData。
工作源参考:https://github.com/SupriyaNaveen/CookingRecipes


6

如果您的语句成功,插入一条记录的返回值将为1。

如果您想插入对象列表,可以使用以下方法:

@Insert(onConflict = OnConflictStrategy.REPLACE)
public long[] addAll(List<Object> list);

使用Rx2来执行它:

Observable.fromCallable(new Callable<Object>() {
        @Override
        public Object call() throws Exception {
            return yourDao.addAll(list<Object>);
        }
    }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Object>() {
        @Override
        public void accept(@NonNull Object o) throws Exception {
           // the o will be Long[].size => numbers of inserted records.

        }
    });

4
根据这份文档:https://developer.android.com/training/data-storage/room/accessing-data,如果@Insert方法只接收一个参数,它可以返回long类型,即插入项的新rowId。如果参数是数组或集合,则应返回long[]或List<Long>。如果您的语句成功插入一条记录,则插入的返回值将为1。 - CodeClown42

6

经过很多的努力,我终于解决了这个问题。以下是我的解决方案,采用MMVM架构:

Student.kt

@Entity(tableName = "students")
data class Student(
    @NotNull var name: String,
    @NotNull var password: String,
    var subject: String,
    var email: String

) {

    @PrimaryKey(autoGenerate = true)
    var roll: Int = 0
}

StudentDao.kt

interface StudentDao {
    @Insert
    fun insertStudent(student: Student) : Long
}

StudentRepository.kt

    class StudentRepository private constructor(private val studentDao: StudentDao)
    {

        fun getStudents() = studentDao.getStudents()

        fun insertStudent(student: Student): Single<Long>? {
            return Single.fromCallable(
                Callable<Long> { studentDao.insertStudent(student) }
            )
        }

 companion object {

        // For Singleton instantiation
        @Volatile private var instance: StudentRepository? = null

        fun getInstance(studentDao: StudentDao) =
                instance ?: synchronized(this) {
                    instance ?: StudentRepository(studentDao).also { instance = it }
                }
    }
}

StudentViewModel.kt

class StudentViewModel (application: Application) : AndroidViewModel(application) {

var status = MutableLiveData<Boolean?>()
private var repository: StudentRepository = StudentRepository.getInstance( AppDatabase.getInstance(application).studentDao())
private val disposable = CompositeDisposable()

fun insertStudent(student: Student) {
        disposable.add(
            repository.insertStudent(student)
                ?.subscribeOn(Schedulers.newThread())
                ?.observeOn(AndroidSchedulers.mainThread())
                ?.subscribeWith(object : DisposableSingleObserver<Long>() {
                    override fun onSuccess(newReturnId: Long?) {
                        Log.d("ViewModel Insert", newReturnId.toString())
                        status.postValue(true)
                    }

                    override fun onError(e: Throwable?) {
                        status.postValue(false)
                    }

                })
        )
    }
}

在片段中:
class RegistrationFragment : Fragment() {
    private lateinit var dataBinding : FragmentRegistrationBinding
    private val viewModel: StudentViewModel by viewModels()

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initialiseStudent()
        viewModel.status.observe(viewLifecycleOwner, Observer { status ->
            status?.let {
                if(it){
                    Toast.makeText(context , "Data Inserted Sucessfully" , Toast.LENGTH_LONG).show()
                    val action = RegistrationFragmentDirections.actionRegistrationFragmentToLoginFragment()
                    Navigation.findNavController(view).navigate(action)
                } else
                    Toast.makeText(context , "Something went wrong" , Toast.LENGTH_LONG).show()
                //Reset status value at first to prevent multitriggering
                //and to be available to trigger action again
                viewModel.status.value = null
                //Display Toast or snackbar
            }
        })

    }

    fun initialiseStudent() {
        var student = Student(name =dataBinding.edName.text.toString(),
            password= dataBinding.edPassword.text.toString(),
            subject = "",
            email = dataBinding.edEmail.text.toString())
        dataBinding.viewmodel = viewModel
        dataBinding.student = student
    }
}

我已经使用了数据绑定。这是我的XML:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="student"
            type="com.kgandroid.studentsubject.data.Student" />

        <variable
            name="listener"
            type="com.kgandroid.studentsubject.view.RegistrationClickListener" />

        <variable
            name="viewmodel"
            type="com.kgandroid.studentsubject.viewmodel.StudentViewModel" />

    </data>


    <androidx.core.widget.NestedScrollView
        android:id="@+id/nestedScrollview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true"
        tools:context="com.kgandroid.studentsubject.view.RegistrationFragment">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/constarintLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:isScrollContainer="true">

            <TextView
                android:id="@+id/tvRoll"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:gravity="center_horizontal"
                android:text="Roll : 1"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <EditText
                android:id="@+id/edName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Name"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/tvRoll" />

            <TextView
                android:id="@+id/tvName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="16dp"
                android:layout_marginEnd="16dp"
                android:text="Name:"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintBaseline_toBaselineOf="@+id/edName"
                app:layout_constraintEnd_toStartOf="@+id/edName"
                app:layout_constraintStart_toStartOf="parent" />

            <TextView
                android:id="@+id/tvEmail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Email"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintBaseline_toBaselineOf="@+id/edEmail"
                app:layout_constraintEnd_toStartOf="@+id/edEmail"
                app:layout_constraintStart_toStartOf="parent" />

            <EditText
                android:id="@+id/edEmail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Name"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edName" />

            <TextView
                android:id="@+id/textView6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Password"
                android:textColor="@color/colorPrimary"
                android:textSize="18sp"
                app:layout_constraintBaseline_toBaselineOf="@+id/edPassword"
                app:layout_constraintEnd_toStartOf="@+id/edPassword"
                app:layout_constraintStart_toStartOf="parent" />

            <EditText
                android:id="@+id/edPassword"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="16dp"
                android:ems="10"
                android:inputType="textPersonName"
                android:text="Name"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edEmail" />

            <Button
                android:id="@+id/button"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="32dp"
                android:layout_marginTop="24dp"
                android:layout_marginEnd="32dp"
                android:background="@color/colorPrimary"
                android:text="REGISTER"
                android:onClick="@{() -> viewmodel.insertStudent(student)}"
                android:textColor="@android:color/background_light"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edPassword" />
        </androidx.constraintlayout.widget.ConstraintLayout>


    </androidx.core.widget.NestedScrollView>
</layout>

我曾经在使用asynctask时遇到了很多困难,因为room的插入和删除操作必须在单独的线程中完成。最终,我通过使用RxJava中的Single类型的observable解决了这个问题。

以下是rxjava的Gradle依赖项:

implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation 'io.reactivex.rxjava2:rxjava:2.0.3' 

4
如果@Insert方法只接收一个参数,它可以返回一个long值,该值是插入项的新rowId。详情请见此链接
@Insert
suspend fun insert(myEntity: MyEntity):Long

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接