一、Room 框架基础
1. 实体类(Entity)
实体类用于描述数据库表的结构。通过使用 @Entity
注解,可以将一个 Java 类映射到数据库中的一张表。例如,以下是一个简单的 User
实体类:
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
public String name;
public int age;
}
在这个例子中,@Entity
注解将 User
类标记为数据库实体,@PrimaryKey
注解指定了 id
字段为主键,并且 autoGenerate = true
表示主键将自动生成。
2. 数据访问对象(DAO)
DAO 接口定义了对数据库的操作方法。通过使用 @Query
、@Insert
、@Update
和 @Delete
等注解,可以方便地执行 SQL 查询和数据的增删改操作。以下是一个简单的 UserDao
接口示例:
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface UserDao {
@Insert
void insertUser(User user);
@Query("SELECT * FROM User")
List<User> getAllUsers();
}
在这个例子中,@Insert
注解用于插入数据,@Query
注解用于执行 SQL 查询语句。
3. 数据库类(Database)
数据库类负责创建和管理数据库实例。通过继承 RoomDatabase
类,并使用 @Database
注解指定实体类和数据库版本,可以创建一个 Room 数据库。以下是一个简单的 AppDatabase
类示例:
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
在这个例子中,@Database
注解指定了 User
类为数据库实体,版本号为 1。userDao()
方法用于获取 UserDao
实例。
二、Room 中实现 Entity 嵌套 Entity 创建相应的表
1. 定义相关实体类
假设我们有一个Author
(作者)实体类和一个Book
(书籍)实体类,每一个Author
可以有多本Book
,我们希望在查询Author
时能同时获取其相关的Book
信息。
Author
实体类
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.util.List;
@Entity
public class Author {
@PrimaryKey
public int id;
public String name;
// 这里的books是与该作者相关的书籍列表,在后续会通过@Relation注解关联
public List<Book> books;
}
Book
实体类
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.PrimaryKey;
@Entity(foreignKeys = @ForeignKey(entity = Author.class,
parentColumns = "id",
childColumns = "authorId"))
public class Book {
@PrimaryKey
public int id;
public String title;
// 关联作者的id
public int authorId;
}
2. 定义包含关系的 POJO(Plain Old Java Object)类
为了更好地处理嵌套关系,我们创建一个 POJO 类,用于将Author
和其相关的Book
进行组合。
import androidx.room.Embedded;
import androidx.room.Relation;
import java.util.List;
public class AuthorWithBooks {
// 将Author实体类嵌入到AuthorWithBooks中
@Embedded
public Author author;
// 通过外键关联,获取与该作者相关的书籍列表
@Relation(
parentColumn = "id",
entityColumn = "authorId"
)
public List<Book> books;
}
在上述代码中:
@Embedded
注解用于将一个实体类嵌入到另一个类中,这里将Author
实体嵌入到AuthorWithBooks
中。@Relation
注解用于定义两个实体之间的关系,parentColumn
指定父实体(这里是Author
)的列,entityColumn
指定子实体(这里是Book
)的列,通过这两个列的关联来获取相关数据。
3. 定义数据访问对象(DAO)接口
在 DAO 接口中定义查询方法,用于获取AuthorWithBooks
数据。
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
@Dao
public interface AuthorDao {
@Query("SELECT * FROM Author")
List<AuthorWithBooks> getAuthorsWithBooks();
}
这里的查询语句只是简单地从Author
表中查询数据,Room 会根据AuthorWithBooks
类中的关系注解,自动关联并获取相关的Book
数据。
4. 定义 Room 数据库类
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {Author.class, Book.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AuthorDao authorDao();
}
5. 使用示例
import androidx.room.Room;
public class MainActivity {
public static void main(String[] args) {
AppDatabase db = Room.databaseBuilder(
, AppDatabase.class, "my - database")
.build();
AuthorDao authorDao = db.authorDao();
List<AuthorWithBooks> authorsWithBooks = authorDao.getAuthorsWithBooks();
for (AuthorWithBooks authorWithBooks : authorsWithBooks) {
System.out.println("作者:" + authorWithBooks.author.name);
for (Book book : authorWithBooks.books) {
System.out.println(" 书籍:" + book.title);
}
}
}
}
三、Room的多表查询
1. 多表查询 SQL 语句示例(以常见的 MySQL 为例,假设存在学生表 students、课程表 courses、成绩表 scores)
假设 students 表有字段 id(学生编号)、name(学生姓名);courses 表有字段 id(课程编号)、course_name(课程名称);scores 表有字段 id(成绩记录编号)、student_id(关联学生表的学生编号)、course_id(关联课程表的课程编号)、score(成绩) 。
要查询每个学生的姓名、所选课程名称以及对应的成绩,SQL 语句如下:
SELECT
s.name,
c.course_name,
sc.score
FROM
students s
-- 使用INNER JOIN(内连接)关联成绩表scores
JOIN
scores sc ON s.id = sc.student_id
-- 使用INNER JOIN关联课程表courses
JOIN
courses c ON sc.course_id = c.id;
解释:
SELECT
子句指定要查询的列,这里选择学生姓名s.name
、课程名称c.course_name
和成绩sc.score
。FROM
子句指定主表,这里以students
表为主表,起别名s
。JOIN
用于连接其他表,ON
子句指定连接条件。第一个JOIN
将students
表和scores
表通过学生编号关联起来;第二个JOIN
将scores
表和courses
表通过课程编号关联起来,从而实现多表数据的联合查询。
2. 使用 Room(Android 中的数据库框架)实现类似多表查询
首先,定义相关的实体类:
- 学生实体类 Student:
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class Student {
@PrimaryKey
public int id;
public String name;
}
- 课程实体类 Course:
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class Course {
@PrimaryKey
public int id;
public String course_name;
}
- 成绩实体类 Score:
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.PrimaryKey;
@Entity(foreignKeys = {
@ForeignKey(entity = Student.class, parentColumns = "id", childColumns = "student_id"),
@ForeignKey(entity = Course.class, parentColumns = "id", childColumns = "course_id")
})
public class Score {
@PrimaryKey
public int id;
public int student_id;
public int course_id;
public int score;
}
然后,定义数据访问对象(DAO)接口,在其中定义查询方法:
import androidx.room.Dao;
import androidx.room.Query;
import java.util.List;
@Dao
public interface ScoreDao {
@Query("SELECT s.name, c.course_name, sc.score " +
"FROM Student s " +
"JOIN Score sc ON s.id = sc.student_id " +
"JOIN Course c ON sc.course_id = c.id")
List<Object[]> getStudentCourseScore();
}
这里的 @Query
注解中写的 SQL 语句和前面纯 SQL 的多表查询思路一致。由于查询结果涉及多个表的不同列,返回类型定义为 List<Object[]>
,每一个 Object[]
数组代表一条查询结果记录,数组中的元素依次对应查询的列(学生姓名、课程名称、成绩) 。
在使用时,通过 Room 数据库实例获取 ScoreDao
的实例并调用该查询方法:
import androidx.room.Room;
public class MainActivity {
public static void main(String[] args) {
AppDatabase db = Room.databaseBuilder(
, AppDatabase.class, "my - database")
.build();
ScoreDao scoreDao = db.scoreDao();
List<Object[]> result = scoreDao.getStudentCourseScore();
for (Object[] row : result) {
String studentName = (String) row[0];
String courseName = (String) row[1];
int studentScore = (int) row[2];
System.out.println("学生:" + studentName + ",课程:" + courseName + ",成绩:" + studentScore);
}
}
}
四、Room 与直接使用 SQLite 的区别
一、Room 的核心用处
Room 是 Android Jetpack 组件中的持久化库,通过抽象 SQLite 的底层操作,提供更安全、高效、简洁的数据库访问方式。其核心价值体现在:
-
简化数据库操作
- 注解驱动:通过
@Entity
、@Dao
、@Database
等注解自动生成模板代码。 - 消除样板代码:无需手动编写
SQLiteOpenHelper
和Cursor
处理逻辑。 - 示例对比:
// Room 插入数据(1行) @Insert void insertUser(User user); // 传统 SQLite 插入数据(约20行) // 需手动处理 ContentValues、SQL 语句、异常等
- 注解驱动:通过
-
提升开发效率
- 编译时校验:SQL 查询在编译时检查语法错误(传统 SQLite 需运行时调试)。
- 类型安全:查询结果直接映射到 Java/Kotlin 对象(无需解析
Cursor
)。 - 关系映射:通过
@Relation
和@Embedded
简化嵌套查询(如一对多关系)。
-
保障数据安全
- 防 SQL 注入:参数化查询自动转义输入值(传统 SQLite 需手动处理)。
- 事务封装:通过
@Transaction
注解简化复杂事务操作。
-
优化性能
- 最小化重建:
LiveData
+Room
实现数据变化时自动更新 UI(避免全量刷新)。 - 异步支持:内置
RxJava
/Coroutines
支持,避免主线程阻塞。
- 最小化重建:
二、Room 的核心特性总结
特性 | Room 的实现方式 | 传统 SQLite 的痛点 |
---|---|---|
实体定义 | @Entity 注解自动建表 | 需手动编写 CREATE TABLE 语句 |
数据库操作 | @Dao 接口声明增删改查方法 | 需手写 ContentValues 和 Cursor |
数据库管理 | @Database 注解自动管理版本升级 | 需重写 onUpgrade() 处理迁移逻辑 |
关系映射 | @Relation + @Embedded 实现嵌套查询 | 需手写多表 JOIN 和对象组装逻辑 |
线程安全 | 强制主线程外操作(否则抛出异常) | 需手动管理线程切换 |
类型安全 | 编译时 SQL 校验 | 运行时才能发现 SQL 语法错误 |
三、Room 的典型使用场景
-
复杂数据关系管理
- 示例:电商应用的「用户-订单-商品」三级嵌套(通过
@Relation
一键查询)。
@Relation(parentColumn = "userId", entityColumn = "orderUserId") List<Order> orders;
- 示例:电商应用的「用户-订单-商品」三级嵌套(通过
-
响应式数据流
- 结合
LiveData
实现数据变动自动刷新 UI:
@Query("SELECT * FROM User") LiveData<List<User>> getAllUsers(); // UI 自动更新
- 结合
-
本地缓存与离线支持
- 网络数据持久化存储,通过
Repository
模式统一数据源:
public class UserRepository { private UserDao userDao; public void saveUser(User user) { userDao.insert(user); // 数据库持久化 } }
- 网络数据持久化存储,通过
四、Room vs 直接使用 SQLite 的核心优势
维度 | Room | 原生 SQLite |
---|---|---|
开发效率 | 注解自动生成代码 | 需手写大量模板代码 |
可维护性 | 清晰的 DAO/Entity 分层结构 | 逻辑分散在 SQLiteOpenHelper 中 |
安全性 | 编译时检查 + 防 SQL 注入 | 需手动处理参数转义 |
性能 | 内置优化(如惰性加载) | 依赖开发者优化能力 |
学习成本 | 只需掌握注解规则 | 需深入理解 SQLite API |