ROOM Database

ROOM Database

温馨提示:本文最后更新于2025-11-25 17:46:22,某些文章具有时效性,若有错误或已失效,请在下方留言

Room DatabaseAndroid 官方提供的本地数据库框架,是 Jetpack 架构组件的一部分,用于在应用中以安全、简洁、高效的方式存储本地数据。它在 SQLite 之上提供了更高层的抽象,让开发者更容易读写数据库,并减少样板代码。

核心概念

Room Database 有三个主要的核心概念

  • Entity
  • DAO(Database Access Object)
  • Database

Entity 实体

  • 对应数据库中的一张表
  • 每个字段对应表中的一列
  • 使用 @Entity 注解定义
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity
public class User {
    @PrimaryKey(autoGenerate = true)
    public int uid;

    public String name;
    public int age;
}

DAO 数据库访问对象

  • 定义访问数据库的方法(增删改查)
  • Room 会根据 DAO 自动生成实际 SQL 代码
  • 使用 @Dao 注解
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;

import java.util.List;

@Dao
public interface UserDao {
    @Query("SELECT * FROM User")
    List<User> getAll();

    @Insert
    void insert(User user);

    @Delete
    void delete(User user);
}

Database 数据库定义

  • 继承 RoomDatabase
  • 使用 @Database 注解
  • 将所有 EntityDAO 注册进去
import androidx.room.Database;
import androidx.room.RoomDatabase;

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

代码示例

1. 声明依赖

bundle.gradle.kts 文件中,添加 ROOM 相关的依赖。

dependencies {
    implementation(libs.appcompat)
    implementation(libs.material)
    implementation(libs.activity)
    implementation(libs.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)

    // ROOM 相关的声明
    val room_version = "2.8.4"
    implementation("androidx.room:room-runtime:$room_version")
    // If this project only uses Java source, use the Java annotationProcessor
    annotationProcessor("androidx.room:room-compiler:$room_version")
}

添加完成后,点击 Sync Now 按钮,开始同步配置。

2. 创建 Entity 实体

Entity 是表示 SQLite 数据库中的表的基本组件。新建 Contacts 实体,实体包含 idname 以及 email

package com.stewednoodles.contactsmanagerapp;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

/**
 * 实体类
 * @Entity: 将 tableName 与实体类 Contacts 进行映射
 */
@Entity(tableName = "contacts_table")
public class Contacts {
    /**
     * @ColumnInfo: 将列名与实体类中的字段进行映射
     * @PrimaryKey: 设置主键
     */
    @ColumnInfo(name = "contact_id") // 设置列名 数据库表中的 contact_id 与 Contacts 实体的 id 进行 映射
    @PrimaryKey(autoGenerate = true) // 设置 主键,Entity 中必须设置主键
    private int id;

    @ColumnInfo(name = "contact_name")
    private String name;

    @ColumnInfo(name = "contact_email")
    private String email;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Contacts(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    public Contacts() {
    }
}

3. 创建 DAO

DAO 是一个接口,定义了一系列用于对 Entity 数据库表执行数据库操作的方法,例如 insetupdatedeletequery 以及其他操作。

DAO 是一个用 @Dao 注解的接口。

package com.stewednoodles.contactsmanagerapp;

import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;

import java.util.List;

@Dao
public interface ContactDAO {
    /**
     * 插入数据
     * @param contact
     */
    @Insert
    void insert(Contacts contact);

    /**
     * 删除数据
     * @param contact
     */
    @Delete
    void delete(Contacts contact);


    @Query("SELECT * FROM contacts_table")
    List<Contacts> getAllContacts();
}

4. 创建 Database 类

Database 类是一个抽象类,用作数据库的持有者。

package com.stewednoodles.contactsmanagerapp;

import android.content.Context;

import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

/**
 * 定义了数据库中的实体(表)和版本号
 *  - 数据库变更时,可以更新版本号
 */
@Database(entities = {Contacts.class}, version = 1)
public abstract class ContactDatabase extends RoomDatabase {
    /**
     * 获取 DAO
     * @return DAO 对象
     */
    public abstract ContactDAO getContactDAO();

    // Database 需要遵循单例模式
    private static ContactDatabase instance;

    /**
     * 获取数据库实例
     * @param context 上下文
     * @return 数据库实例
     */
    public static synchronized ContactDatabase getInstance(Context context) {
        if (instance == null) {
            instance = Room.databaseBuilder(
                    context.getApplicationContext(),
                    ContactDatabase.class,
                    "contacts_db"
            ).fallbackToDestructiveMigration().build();
        }

        return instance;
    }

}

5. 创建 Repository

Repository 可以从不同的数据源(不同的 REST API、缓存、本地数据库存储)收集数据,并将这些数据提供给应用程序的其余部分。有效递将数据源与应用程序的其他部分隔离开来,并为应用程序的其余部分提供一个干净的数据访问 API。

DAO 中的方法从 Repository 执行。

package com.stewednoodles.contactsmanagerapp;

import java.util.List;

public class Repository {
    // 提供有效的数据源
    // - ROOM Database
    private final ContactDAO contactDAO;

    public Repository(ContactDAO contactDAO) {
        this.contactDAO = contactDAO;
    }

    /**
     * DAO 中的方法从 Repository 执行
     */

    // 添加联系人
    public void addContact(Contacts contact) {
        contactDAO.insert(contact);
    }

    // 删除联系人
    public void deleteContact(Contacts contact) {
        contactDAO.delete(contact);
    }

    // 获取所有联系人
    public List<Contacts> getAllContacts() {
        return contactDAO.getAllContacts();
    }
}

6. Handler、Executor & Runnable

数据库操作,不能放在主线程,否则会阻塞 UI

package com.stewednoodles.contactsmanagerapp;

import android.os.Handler;
import android.os.Looper;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Repository {
    // 提供有效的数据源
    // - ROOM Database
    private final ContactDAO contactDAO;
    ExecutorService executor;
    Handler handler;

    public Repository(ContactDAO contactDAO) {
        this.contactDAO = contactDAO;
        // 用于数据库的后台操作
        executor = Executors.newSingleThreadExecutor();
        // 用于前台 UI 更新
        handler = new Handler(Looper.getMainLooper());
    }

    /**
     * DAO 中的方法从 Repository 执行
     */

    // 添加联系人
    public void addContact(Contacts contact) {
        // Runnable: 在独立线程运行任务
        executor.execute(new Runnable() {
            @Override
            public void run() {
                // 在独立的线程中,异步执行
                contactDAO.insert(contact);
            }
        });

    }

    // 删除联系人
    public void deleteContact(Contacts contact) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                contactDAO.delete(contact);
            }
        });
    }

    // 获取所有联系人
    public List<Contacts> getAllContacts() {
        return contactDAO.getAllContacts();
    }
}

Handler

Handler 的作用是在特定线程(通常是主线程)中处理消息或执行代码。主要用途:

  • 主线程更新 UI
  • 切换新城
  • 发送消息、延迟执行任务
Handler handler = new Handler(Looper.getMainLooper());

handler.post(new Runnable() {
    @Override
    public void run() {
        // 在主线程执行
    }
});

Executor

Executor 是一个执行 Runnable 的线程池框架。

Executor executor = Executors.newSingleThreadExecutor();

executor.execute(new Runnable() {
    @Override
    public void run() {
        // 在线程池的线程中执行
    }
});

Runnable

Runnable 是一个接口,表示可以在线程中执行的一段代码。

Runnable task = new Runnable() {
    @Override
    public void run() {
        // 要在线程中执行的代码
    }
};

7. Live Data

Live Data 通常用于 RepositoryDAO 中,以便观察数据库的更改,并为 UI 提供实时更新。

package com.stewednoodles.contactsmanagerapp;

import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;

import java.util.List;

@Dao
public interface ContactDAO {
    /**
     * 插入数据
     * @param contact
     */
    @Insert
    void insert(Contacts contact);

    /**
     * 删除数据
     * @param contact
     */
    @Delete
    void delete(Contacts contact);


    @Query("SELECT * FROM contacts_table")
    LiveData<List<Contacts>> getAllContacts();
}
package com.stewednoodles.contactsmanagerapp;

import android.app.Application;
import android.os.Handler;
import android.os.Looper;

import androidx.lifecycle.LiveData;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Repository {
    // 提供有效的数据源
    // - ROOM Database
    private final ContactDAO contactDAO;
    ExecutorService executor;
    Handler handler;

    public Repository(Application application) {
        ContactDatabase instance = ContactDatabase.getInstance(application);
        this.contactDAO = instance.getContactDAO();
        // 用于数据库的后台操作
        executor = Executors.newSingleThreadExecutor();
        // 用于前台 UI 更新
        handler = new Handler(Looper.getMainLooper());
    }

    /**
     * DAO 中的方法从 Repository 执行
     */

    // 添加联系人
    public void addContact(Contacts contact) {
        // Runnable: 在独立线程运行任务
        executor.execute(new Runnable() {
            @Override
            public void run() {
                // 在独立的线程中,异步执行
                contactDAO.insert(contact);
            }
        });

    }

    // 删除联系人
    public void deleteContact(Contacts contact) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                contactDAO.delete(contact);
            }
        });
    }

    // 获取所有联系人
    public LiveData<List<Contacts>> getAllContacts() {
        return contactDAO.getAllContacts();
    }


}

8. ViewModel

ViewModel 的作用是获取并保存 ActivityFragment 所需要的信息。它作为 ModelView 之间的连接桥梁,负责将来自 Model 的数据进行转换,并向 View 提供数据流。

ActivityFragment 应当能够观察 ViewModel 中的数据变化。ViewModel 通常通过 LiveDataAndroid Data Binding 来暴露这些信息。

package com.stewednoodles.contactsmanagerapp;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

public class MyViewModel extends AndroidViewModel {
    private Repository repository;

    public MyViewModel(@NonNull Application application) {
        super(application);
        this.repository = new Repository(application);;
    }

    public void addContact(Contacts contact) {
        repository.addContact(contact);
    }

    public void deleteContact(Contacts contact) {
        repository.deleteContact(contact);
    }

    public LiveData<List<Contacts>> getAllContacts() {
        return repository.getAllContacts();
    }
}

9. View

开启 Data Binding

buildFeatures {
    dataBinding = true
}

页面布局

主页面布局文件的内容

<?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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycleView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/floatingActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="35dp"
            android:layout_marginBottom="40dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

联系人列表页面布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text_view_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="Name"
            android:textSize="32sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/text_view_email"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="Email"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/text_view_name" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

10. 数据绑定

列表页面数据绑定的代码,如下所示

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <!--  数据绑定  -->
    <data>
        <variable
            name="contact"
            type="com.stewednoodles.contactsmanagerapp.Contacts" />
    </data>
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_margin="16dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text_view_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{contact.name}"
            android:textSize="32sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/text_view_email"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{contact.email}"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/text_view_name" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

主页面悬浮按钮点击事件的绑定

<?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="clickHandler"
            type="com.stewednoodles.contactsmanagerapp.MainActivityClickHandler" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycleView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:onClick="@{clickHandler::onFABClick}"
            android:id="@+id/floatingActionButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="35dp"
            android:layout_marginBottom="40dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

RecyclerView 数据绑定

RecyclerView 数据绑定的代码,如下所示

package com.stewednoodles.contactsmanagerapp;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.databinding.ViewDataBinding;
import androidx.recyclerview.widget.RecyclerView;

import com.stewednoodles.contactsmanagerapp.databinding.ContactListItemBinding;

import java.util.ArrayList;
import java.util.List;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ContactViewHolder> {

    private ArrayList<Contacts> contacts;

    public MyAdapter(ArrayList<Contacts> contacts) {
        this.contacts = contacts;
    }

    public void setContacts(ArrayList<Contacts> contacts) {
        this.contacts = contacts;

        // 通知关联的 RecyclerView,其底层数据集已经发生变化,RecyclerView 应刷新其视图以反映这些变化。
        notifyDataSetChanged();
    }

    @NonNull
    @Override
    public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        ContactListItemBinding binding = DataBindingUtil.inflate(
                LayoutInflater.from(parent.getContext()),
                R.layout.contact_list_item, parent,
                false
        );
        return new ContactViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
        Contacts contact = contacts.get(position);
        holder.contactListItemBinding.setContact(contact);
    }

    @Override
    public int getItemCount() {
        return contacts != null ? contacts.size() : 0;
    }

    class ContactViewHolder extends RecyclerView.ViewHolder {
        private ContactListItemBinding contactListItemBinding;

        public ContactViewHolder(@NonNull ContactListItemBinding contactListItemBinding) {
            super(contactListItemBinding.getRoot());
            this.contactListItemBinding = contactListItemBinding;
        }
    }
}
© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容