Android Room 数据库详解【使用篇】

目录

[一、什么是 Room?](#一、什么是 Room?)

二、添加依赖

三、完整代码示例

[3.1 创建实体类 (Entity)](#3.1 创建实体类 (Entity))

[3.2 创建 DAO 接口](#3.2 创建 DAO 接口)

[3.2.1 插入操作(@Insert)](#3.2.1 插入操作(@Insert))

[(1) 处理冲突策略 (OnConflictStrategy)](#(1) 处理冲突策略 (OnConflictStrategy))

[(2) 返回值类型](#(2) 返回值类型)

[3.2.2 更新操作 (@Update)](#3.2.2 更新操作 (@Update))

[3.2.3 删除操作 (@Delete)](#3.2.3 删除操作 (@Delete))

[3.2.4 万能查询 (@Query)](#3.2.4 万能查询 (@Query))

[(1) 基础查询](#(1) 基础查询)

[(2) 带参数查询 (使用冒号 :)](#(2) 带参数查询 (使用冒号 :))

[(3) 返回部分列 (Projection)](#(3) 返回部分列 (Projection))

[3.3 创建数据库类(RoomDatabase)](#3.3 创建数据库类(RoomDatabase))

[3.3.1 RoomDatabase](#3.3.1 RoomDatabase)

[3.3.2 @Database 注解详解](#3.3.2 @Database 注解详解)

[3.3.3 两种核心构建器 (Factory Methods)](#3.3.3 两种核心构建器 (Factory Methods))

[(1) Room.databaseBuilder (最常用)](#(1) Room.databaseBuilder (最常用))

[(2) Room.inMemoryDatabaseBuilder (内存数据库)](#(2) Room.inMemoryDatabaseBuilder (内存数据库))

[3.4 创建 Repository(推荐)](#3.4 创建 Repository(推荐))

[3.5 在 ViewModel 中使用](#3.5 在 ViewModel 中使用)

四、数据迁移


系列入口导航:Android Jetpack 概述

一、什么是 Room?

Room 是 Android Jetpack 组件库中的一个持久化库 ,它在 SQLite 之上提供了一层抽象,让数据库操作更加简单、安全。Room 在编译时会检查 SQL 语句的正确性,减少了运行时错误的风险。

Room 包含三个主要组件

  • Entity(实体):表示数据库中的表
  • DAO(数据访问对象):定义数据库操作方法
  • Database(数据库):数据库持有者,作为主要访问点

想了解关于SQLite 内容可以参考下面链接:

Android内置SQLite的使用(超详细)

二、添加依赖

在 build.gradle (Module) 文件中添加以下依赖:

java 复制代码
dependencies {
    def room_version = "2.7.0" // 请根据当前实际版本调整

    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"

    // 可选:对 RxJava 或 Guava 的支持
    implementation "androidx.room:room-rxjava2:$room_version"
    implementation "androidx.room:room-guava:$room_version"
}

三、完整代码示例

3.1 创建实体类 (Entity)

java 复制代码
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.PrimaryKey;

@Entity(tableName = "users")
public class User {
    
    @PrimaryKey(autoGenerate = true)
    private int id;
    
    @ColumnInfo(name = "user_name")
    private String userName;
    
    @ColumnInfo(name = "email")
    private String email;
    
    @ColumnInfo(name = "age")
    private int age;
    
    @ColumnInfo(name = "created_time")
    private long createdTime;
    
    // 使用 @Ignore 标记 Room 应该忽略的字段
    @Ignore
    private String tempData;
    
    // 必须有一个无参构造方法
    public User() {
    }
    
    // 有参构造方法(Room 会使用这个)
    public User(String userName, String email, int age, long createdTime) {
        this.userName = userName;
        this.email = email;
        this.age = age;
        this.createdTime = createdTime;
    }
    
    // Getters 和 Setters
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getUserName() {
        return userName;
    }
    
    public void setUserName(String userName) {
        this.userName = userName;
    }
    
    public String getEmail() {
        return email;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }
    
    public int getAge() {
        return age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
    
    public long getCreatedTime() {
        return createdTime;
    }
    
    public void setCreatedTime(long createdTime) {
        this.createdTime = createdTime;
    }
    
    public String getTempData() {
        return tempData;
    }
    
    public void setTempData(String tempData) {
        this.tempData = tempData;
    }
}

上面我们创建了一个Entity 类,根据观察可以发现 Entity 类 就像是 Bean 类 + 注解的集合体。

为了更好的理解我们将 SQLite 对应的部分对比下:

java 复制代码
    // 创建数据库
    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建 users 表(与 User 实体类对应)
        String createTableSql = "CREATE TABLE users (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT, " +      // 对应 @PrimaryKey(autoGenerate = true)
                "user_name TEXT, " +                            // 对应 @ColumnInfo(name = "user_name")
                "email TEXT, " +                                // 对应 email 字段
                "age INTEGER, " +                               // 对应 age 字段
                "created_time INTEGER" +                        // 对应 created_time 字段(long 类型)
                ")";
        
        db.execSQL(createTableSql);
        
    }
Room Entity 组件 SQLite 对应部分 说明
@Entity(tableName = "users") CREATE TABLE users (...) 创建名为 users 的表
@PrimaryKey PRIMARY KEY 主键约束
@ColumnInfo 列定义 列的属性信息
字段 (Field) 列 (Column) 每个字段对应一列
@Ignore - 忽略该字段,不创建列

在 Room 出现之前,我们通常需要直接编写SQL语句来创建数据库表。这种方式虽然直观,但存在诸多问题。因为SQL语句是字符串,没有编译时检查,表名或字段名拼写错误只能在运行时发现

那每列的类型是如何适配的呢?字段类型映射如下

Java 类型 SQLite 类型 说明
int, long INTEGER 整型
String TEXT 文本类型
boolean INTEGER 0 = false, 1 = true
float, double REAL 浮点数
byte[] BLOB 二进制数据
Date INTEGER 需要 TypeConverter

3.2 创建 DAO 接口

如果说Entity定义了数据库的"骨骼",那么DAO 就是数据库的"灵魂"。DAO(Data Access Object)封装了所有数据库访问逻辑,是连接业务层和数据库层的桥梁

DAO是一个接口或抽象类,通过注解来定义数据库操作。Room会在编译时自动生成实现类,你不需要写任何实现代码!

java 复制代码
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import java.util.List;

@Dao
public interface UserDao {
    
    // 插入操作
    @Insert
    void insert(User user);
    
    @Insert
    void insertAll(User... users);
    
    // 更新操作
    @Update
    void update(User user);
    
    // 删除操作
    @Delete
    void delete(User user);
    
    // 查询所有用户
    @Query("SELECT * FROM users ORDER BY created_time DESC")
    List<User> getAllUsers();
    
    // 根据 ID 查询用户
    @Query("SELECT * FROM users WHERE id = :userId")
    User getUserById(int userId);
    
    // 根据名称模糊查询
    @Query("SELECT * FROM users WHERE user_name LIKE '%' || :name || '%'")
    List<User> getUsersByName(String name);
    
    // 查询年龄大于指定值的用户
    @Query("SELECT * FROM users WHERE age > :minAge ORDER BY age DESC")
    List<User> getUsersOlderThan(int minAge);
    
    // 统计用户数量
    @Query("SELECT COUNT(*) FROM users")
    int getUserCount();
    
    // 删除所有用户
    @Query("DELETE FROM users")
    void deleteAllUsers();
    
    // 根据年龄范围查询
    @Query("SELECT * FROM users WHERE age BETWEEN :minAge AND :maxAge")
    List<User> getUsersByAgeRange(int minAge, int maxAge);
    
    // 查询最新的 5 个用户
    @Query("SELECT * FROM users ORDER BY created_time DESC LIMIT 5")
    List<User> getLatestUsers();
}

3.2.1 插入操作(@Insert)

在 DAO(Data Access Object)接口中,你只需要在方法上标注 @InsertRoom 会自动为你生成底层 SQL 代码

java 复制代码
@Dao
public interface UserDao {
    @Insert
    long insertUser(User user);

    @Insert
    void insertUsers(List<User> users); // 支持集合
}
(1) 处理冲突策略 (OnConflictStrategy)

这是 @Insert 中最重要的参数。当你要插入的数据主键(Primary Key)与数据库中已有的记录冲突时,你可以决定如何处理。

java 复制代码
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertOrUpdate(User user);
策略 行为
REPLACE 替换旧数据。如果已存在,先删除旧行再插入新行(最常用)。
ABORT 中止并回滚。这是默认值,如果冲突则报错并回滚事务。
IGNORE 忽略冲突。保留旧数据,不插入新数据,也不报错。
(2) 返回值类型

@Insert 方法非常灵活,可以根据需要定义不同的返回值:

  • void:不关心结果。
  • long:返回插入行的 Row ID。如果插入的是集合则返回 long[] 或 List<Long>
  • RxJava/LiveData/Flow:支持异步流返回插入结果。

3.2.2 更新操作 (@Update)

@Update根据 主键 (Primary Key) 来定位数据库中的行并更新非主键字段

  • 匹配机制:它会寻找Entity 对象中主键值对应的行。如果找不到,则不执行任何操作
  • 返回值:可以返回int,代表受影响的行数(成功更新了几条数据)。
java 复制代码
@Update
public int updateUser(User user);

@Update
public void updateUsers(List<User> users);

3.2.3 删除操作 (@Delete)

@Delete同样通过主键 来匹配并删除记录

  • 返回值:可以返回 int,代表成功删除的行数。
  • 注意:你必须传入一个带有正确主键的 Entity 对象,即使你只想删除这一行,也需要把对象传进去(或者至少是一个包含主键的对象)。
java 复制代码
@Delete
public int deleteUser(User user);

@Delete
public int deleteUser(int id); // 不被允许的,错误的

3.2.4 万能查询 (@Query)

这是 Room 的核心。它允许你编写 原生 SQL 语句。查询操作在编译时就会进行语法检查,如果表名或列名写错了,编译器会直接报错。

(1) 基础查询

使用纯 SQL 语句。

java 复制代码
@Query("SELECT * FROM user")
List<User> getAllUsers();
(2) 带参数查询 (使用冒号 :)

你可以直接在 SQL 中引用方法参数

java 复制代码
@Query("SELECT * FROM user WHERE age > :minAge")
List<User> loadUsersOlderThan(int minAge);

@Query("SELECT * FROM user WHERE name LIKE :search OR last_name LIKE :search")
List<User> findUserByName(String search);
(3) 返回部分列 (Projection)

如果你只需要某些字段,可以定义一个简单的 POJO 类:

java 复制代码
public class NameTuple {
    public String name;
    public String lastName;
}

@Query("SELECT name, last_name FROM user")
List<NameTuple> loadNames();
操作 依赖项 常见返回值 特点
@Insert 整个对象 long (RowID) 处理新数据进入。
@Update 主键匹配 int (影响行数) 修改已有记录。
@Delete 主键匹配 int (删除行数) 移除记录。
@Query 原生 SQL 任意对象/List 最灵活,支持多表联查 (JOIN)。

3.3 创建数据库类(RoomDatabase)

java 复制代码
// AppDatabase.java
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    
    private static volatile AppDatabase instance;
    
    public abstract UserDao userDao();
    
    // 单例模式获取数据库实例
    public static AppDatabase getInstance(Context context) {
        if (instance == null) {
            synchronized (AppDatabase.class) {
                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.getApplicationContext(),
                        AppDatabase.class,
                        "app_database"
                    )
                    .allowMainThreadQueries()  // 允许主线程操作(仅用于测试)
                    .build();
                }
            }
        }
        return instance;
    }
}

采用了 单例模式 (Singleton) 来确保整个 App 运行期间只有一个数据库实例。

3.3.1 RoomDatabase

RoomDatabase 是 Room 持久化库的底层底座 ,它在架构中起到了"中央枢纽"的作用。

  • 接入层:它持有了所有的 DAO(Data Access Objects)。
  • 配置层:通过 @Database 注解定义表结构(Entities)和版本号。
  • 管理层:负责数据库连接的创建、缓存以及底层的 SQLite 状态管理。

3.3.2 @Database 注解详解

参数配置:

  • entities:声明数据库中包含的所有表。必须是标注了**@Entity 的类**。
  • version:数据库版本。每当修改表结构(增减列、修改类型),此版本必须递增。
  • exportSchema:如果设为 true,Room 会在编译时导出一个描述表结构的 JSON 文件。

3.3.3 两种核心构建器 (Factory Methods)

在 Room 框架中,Room.databaseBuilder 是最常用的方法,但并不是唯一的方法。

(1) Room.databaseBuilder (最常用)

这是代码中使用的,用于持久化存储

  • 行为:在设备的磁盘上(/data/data/包名/databases/)创建一个真正的数据库文件
  • 场景:正式的业务开发,数据需要永久保存,即使 App 进程杀掉或手机重启,数据依然存在。
java 复制代码
instance = Room.databaseBuilder(
    context.getApplicationContext(),
    AppDatabase.class,
    "db_name" // 数据库文件名
).build();
(2) Room.inMemoryDatabaseBuilder (内存数据库)

这是一个非常实用的方法,用于非持久化存储

  • 行为 :数据存储在内存中。一旦 App 进程被杀掉,所有数据都会消失。

  • 场景

    • 单元测试 (Unit Testing):测试完即焚,不污染手机存储,速度极快。

    • 临时缓存:只需要在 App 运行期间暂存,不希望占用磁盘空间的数据。

方法名称 存储位置 数据持久性 典型场景
databaseBuilder 磁盘文件 永久保存 绝大多数正式业务
inMemoryDatabaseBuilder RAM 内存 进程结束即消失 单元测试、临时缓存
createFromAsset 磁盘 (从 Asset 拷贝) 永久保存 携带初始数据的 App

3.4 创建 Repository(推荐)

这个UserRepository 类扮演的是 "中介中心" 的角色。

它是典型的 Repository 模式(仓库模式) 的实现。简单来说,它的存在是为了把 "数据从哪来""界面怎么显示" 这两件事彻底解耦。

java 复制代码
// UserRepository.java
import android.os.AsyncTask;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class UserRepository {
    
    private final UserDao userDao;
    private final ExecutorService executorService;
    
    public UserRepository(AppDatabase database) {
        this.userDao = database.userDao();
        this.executorService = Executors.newSingleThreadExecutor();
    }
    
    // 插入用户(异步)
    public void insert(User user) {
        executorService.execute(() -> userDao.insert(user));
    }
    
    // 更新用户(异步)
    public void update(User user) {
        executorService.execute(() -> userDao.update(user));
    }
    
    // 删除用户(异步)
    public void delete(User user) {
        executorService.execute(() -> userDao.delete(user));
    }
    
    // 获取所有用户(同步,用于 LiveData 或协程)
    public LiveData<List<User>> getAllUsers() {
        return userDao.getAllUsersLiveData();
    }
}

初始化的时候 this.userDao = database.userDao(); 其中userDao是个抽象方法啊,在哪实现的?

你自己不需要实现它 ,Room 在编译代码时会通过"注解处理器(Annotation Processor)"帮你把实现类写好。这个在后面讲源码的时候会讲解的。

方法类型 是否使用 Executor 行为特征 为什么这么设计?
增/删/改 异步、无返回值 保护主线程,且通常不需要立刻看结果。
查 (Sync) 同步、有返回值 保持灵活性,让外部(ViewModel/协程)来决定什么时候查。

Room 框架有一个硬性安全检查:如果你尝试在主线程(UI 线程)执行任何数据库查询,Room 会直接抛出 IllegalStateException 。虽然我在之前 build 的时候 使用了allowMainThreadQueries(),但是正式开发的时候还是要避免在主线程运行。

3.5 在 ViewModel 中使用

java 复制代码
import android.app.Application;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import java.util.List;

public class UserViewModel extends AndroidViewModel {
    
    private final UserRepository repository;
    // 直接观察数据库的 LiveData
    private final LiveData<List<User>> allUsers;

    public UserViewModel(Application application) {
        super(application);
        AppDatabase database = AppDatabase.getInstance(application);
        repository = new UserRepository(database);
        
        // 关键点:直接从 repository 拿到"活"的数据流
        // 这样只要数据库变了,allUsers 会自动更新,不需要手动 loadUsers()
        allUsers = repository.getAllUsers(); 
    }

    // 供 Activity/Fragment 观察
    public LiveData<List<User>> getUsers() {
        return allUsers;
    }

    public void addUser(String name, String email, int age) {
        User user = new User(name, email, age, System.currentTimeMillis());
        repository.insert(user);
        // 注意:这里不需要再手动调用 loadUsers()!
        // 因为 LiveData 会感应到数据库变化并自动推送新数据。
    }

    public void updateUser(User user) {
        repository.update(user);
        // 同样,LiveData 会自动刷新
    }

    public void deleteUser(User user) {
        repository.delete(user);
        // 同样,LiveData 会自动刷新
    }
}

四、数据迁移

当数据库版本升级时,需要处理迁移:

java 复制代码
// 数据库版本从 1 升级到 2
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        // 添加新列
        database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT DEFAULT ''");
    }
};

// 在构建数据库时添加迁移
instance = Room.databaseBuilder(context, AppDatabase.class, "app_database")
    .addMigrations(MIGRATION_1_2)
    .build();

//既然你增加了 phone 字段,记得同步更新你的 User 实体类!!!!!
  • Migration(1, 2):明确告诉 Room,这段代码负责把数据库从 版本 1 升级到 版本 2

  • database.execSQL(...):这里写的是原生 SQL。Room 在迁移时不支持注解,必须手动操作数据库

  • 注意 :这里的 SQL 语法必须非常精确(例如 TEXT 类型、DEFAULT 值等),要与你在 User 实体类中定义的新字段属性完全一致。

相关推荐
yaoxin5211231 小时前
388. Java IO API - 处理事件
java·服务器·数据库
恋猫de小郭1 小时前
Jetpack Compose 1.11 正式版发布,下一代的全新控件和样式 API,你必须知道
android·前端·flutter
colofullove1 小时前
推导中异常处理
数据库·oracle
Kapaseker1 小时前
Kotlin 的 init 到底咋回事儿?
android·kotlin
黄林晴2 小时前
Compose 四月稳定版来袭,测试、触控、预览工具全线革新
android
黑牛儿2 小时前
MySQL主流存储引擎深度解析:优缺点对比+实操选型指南
数据库·mysql
奋斗tree2 小时前
SQL Server数据库自动备份终极指南方法三
数据库·oracle
克里斯蒂亚诺更新2 小时前
uniapp适配H5和Android-apk实现获取当前位置经纬度并调用接口
android·前端·uni-app
咚咚王者2 小时前
MySQL 导出脚本
android·mysql·adb