Android开发-数据库SQLite

发布于:2025-09-15 ⋅ 阅读:(23) ⋅ 点赞:(0)

一、SQLite 基础:Android 内置的轻量级数据库

1. 什么是 SQLite?

  • 轻量级、嵌入式、零配置的 SQL 数据库引擎
  • 数据存储在设备的私有目录中(/data/data/<package_name>/databases/)。
  • 支持标准的 SQL 语法(如 CREATEINSERTUPDATEDELETESELECT)。
  • 单文件数据库,无需独立的服务器进程。

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 基础上提供了三个核心组件:

  1. @Entity:定义数据库表和实体类。
  2. @Dao (Data Access Object):定义数据访问方法(增删改查)。
  3. @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 是首选

六、动手实践:构建一个笔记应用

  1. 创建 Note Entity(包含 id, title, content, timestamp)。
  2. 定义 NoteDao(增删改查,按标题搜索)。
  3. 创建 NoteDatabase
  4. 使用 ViewModel 和 LiveData 观察数据变化。
  5. 在 RecyclerView 中展示笔记列表

七、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!