一、SQLite 基础:Android 内置的轻量级数据库
1. 什么是 SQLite?
- 轻量级、嵌入式、零配置的 SQL 数据库引擎。
- 数据存储在设备的私有目录中(
/data/data/<package_name>/databases/
)。 - 支持标准的 SQL 语法(如
CREATE
,INSERT
,UPDATE
,DELETE
,SELECT
)。 - 单文件数据库,无需独立的服务器进程。
2. 原生 SQLite 操作(不推荐直接使用)
// 继承 SQLiteOpenHelper
public class MyDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "app.db";
private static final int DB_VERSION = 1;
public MyDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_TABLE_USER = "CREATE TABLE users (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT NOT NULL, " +
"age INTEGER, " +
"email TEXT UNIQUE)";
db.execSQL(CREATE_TABLE_USER);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS users");
onCreate(db);
}
}
⚠️ 问题:代码繁琐、易出错、SQL 字符串拼接风险、无编译时检查。
二、Room 持久化库:现代化的 SQLite 抽象
Room
在 SQLite 基础上提供了三个核心组件:
@Entity
:定义数据库表和实体类。@Dao
(Data Access Object):定义数据访问方法(增删改查)。@Database
:数据库持有者,作为访问底层数据源的入口。
1. 添加依赖
在 app/build.gradle
中添加:
dependencies {
def room_version = "2.6.1"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // For Java
// Kotlin 使用 kapt
// kapt "androidx.room:room-compiler:$room_version"
// 可选:Room Kotlin 协程支持
implementation "androidx.room:room-ktx:$room_version"
// 可选:RxJava 支持
implementation "androidx.room:room-rxjava3:$room_version"
}
2. 定义 Entity(实体类)
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public long id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "age")
public int age;
@ColumnInfo(name = "email", index = true) // 添加索引
public String email;
// 必须提供空参构造函数或 Room 兼容的构造函数
public User() {}
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getter 和 Setter ...
}
✅ 关键注解:
@Entity
:标记为数据库表。@PrimaryKey
:主键,autoGenerate=true
自动增长。@ColumnInfo
:指定列名、是否索引等。@Ignore
:忽略该字段,不存入数据库。
3. 定义 DAO(数据访问对象)
@Dao
public interface UserDao {
// 插入
@Insert
long insertUser(User user); // 返回插入行的主键
@Insert
List<Long> insertUsers(List<User> users); // 批量插入
// 查询
@Query("SELECT * FROM users")
List<User> getAllUsers();
@Query("SELECT * FROM users WHERE age > :minAge")
List<User> getUsersOlderThan(int minAge);
@Query("SELECT * FROM users WHERE name LIKE :name")
List<User> findUsersByName(String name);
@Query("SELECT * FROM users WHERE id = :userId")
User getUserById(long userId);
// 更新
@Update
int updateUser(User user); // 返回更新的行数
// 删除
@Delete
int deleteUser(User user); // 返回删除的行数
@Query("DELETE FROM users WHERE age < :minAge")
int deleteUsersYoungerThan(int minAge);
}
✅ 优势:
@Query
:编写 SQL 查询,支持参数绑定(:paramName
)。@Insert
,@Update
,@Delete
:无需写 SQL,Room 自动生成。- 编译时检查:SQL 语法错误在编译时报错,非运行时崩溃。
4. 定义 Database
@Database(entities = {User.class}, version = 1, exportSchema = false)
@TypeConverters({Converters.class}) // 注册类型转换器
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "app_database";
// 抽象方法,返回 DAO 实例
public abstract UserDao userDao();
// 单例模式
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABASE_NAME)
// .allowMainThreadQueries() // ❌ 仅用于测试,禁止在主线程查询
.addCallback(roomCallback) // 数据库创建/升级回调
.build();
}
}
}
return INSTANCE;
}
// 数据库回调
private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
// 数据库创建时执行,如插入初始数据
new PopulateDbAsync(INSTANCE).execute();
}
};
// 异步填充初始数据
static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {
private UserDao userDao;
PopulateDbAsync(AppDatabase db) {
userDao = db.userDao();
}
@Override
protected Void doInBackground(Void... voids) {
userDao.insertUser(new User("张三", 25, "zhangsan@email.com"));
userDao.insertUser(new User("李四", 30, "lisi@email.com"));
return null;
}
}
}
三、高级特性与最佳实践
1. 类型转换器(TypeConverters)
Room 默认不支持存储复杂类型(如 Date
, List<String>
, 自定义对象)。
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
// 存储 List<String> 为 JSON 字符串
@TypeConverter
public static List<String> fromString(String value) {
if (value == null || value.isEmpty()) return Collections.emptyList();
return Arrays.asList(value.split(","));
}
@TypeConverter
public static String toString(List<String> list) {
return list == null ? null : TextUtils.join(",", list);
}
}
在 @Database
上使用 @TypeConverters({Converters.class})
。
2. 数据库升级(Migration)
当 version
增加时,必须提供 Migration
策略。
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// 例如:添加新列
database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
}
};
// 在 databaseBuilder 中添加
.addMigrations(MIGRATION_1_2)
⚠️ 重要:必须为每一对版本(如 1->2, 2->3)提供 Migration,或使用
fallbackToDestructiveMigration()
(慎用,会丢失数据)。
3. 异步操作(推荐)
绝对禁止在主线程进行数据库操作!
(1) 使用 ExecutorService
private ExecutorService executor = Executors.newFixedThreadPool(4);
executor.execute(() -> {
User user = AppDatabase.getInstance(context).userDao().getUserById(1);
// 在子线程中处理结果,再切回主线程更新 UI
});
(2) 使用 Kotlin 协程(推荐)
// 在 DAO 中返回 Flow 或 suspend 函数
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>> // Flow 支持实时更新
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUserById(id: Long): User
(3) 使用 RxJava
@Query("SELECT * FROM users")
Observable<List<User>> getAllUsers();
4. 关系查询(@Relation)
Room 支持简单的关联查询(1对1,1对多)。
public class UserWithBooks {
@Embedded
public User user;
@Relation(
parentColumn = "id",
entityColumn = "user_id"
)
public List<Book> books;
}
5. 全文搜索(FTS)
使用 @Fts3
或 @Fts4
注解创建全文搜索表。
四、最佳实践总结
实践 | 说明 |
---|---|
✅ 使用 Room 而非原生 SQLite | 更安全、更高效、更易维护 |
✅ 异步操作 | 使用协程、RxJava 或 Executor |
✅ 数据库版本管理 | 提供 Migration ,避免数据丢失 |
✅ 合理使用索引 | 在频繁查询的列上添加 index = true |
✅ 避免 N+1 查询 | 使用 @Relation 或 JOIN 查询一次性获取关联数据 |
✅ 编译时检查 | 依赖 room-compiler ,及时发现 SQL 错误 |
✅ 数据观察 | 使用 LiveData 或 Flow 实现数据变更自动刷新 UI |
五、Room vs 原生 SQLite vs 其他方案
方案 | 优点 | 缺点 | 推荐度 |
---|---|---|---|
原生 SQLite | 完全控制,性能极致 | 代码繁琐,易出错,无编译检查 | ⭐ |
Room | 类型安全,编译检查,抽象层,性能好 | 需学习注解 | ⭐⭐⭐⭐⭐ |
Realm | 实时同步,跨平台,易用 | 商业许可,APK 体积大 | ⭐⭐⭐ |
ObjectBox | 速度极快,NoSQL | 学习成本,社区较小 | ⭐⭐⭐ |
✅ 结论:对于绝大多数 Android 应用,Room 是首选。
六、动手实践:构建一个笔记应用
- 创建
Note
Entity(包含 id, title, content, timestamp)。 - 定义
NoteDao
(增删改查,按标题搜索)。 - 创建
NoteDatabase
。 - 使用
ViewModel
和LiveData
观察数据变化。 - 在
RecyclerView
中展示笔记列表。
七、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!