Android 数据持久化学习笔记 —— 数据库存储
安卓系统中内置了一个轻量级的关系型数据库 SQLite,对应的数据库管理类为 SQLiteDatabase。
SQLiteDatabase 数据库管理类
Context 和 SQLiteDatabase 都提供了创建和打开数据库的方法,但两个类中的同名方法所需参数不完全相同,以 openOrCreateDatabase()
为例。
// Context 提供的 openOrCreateDatabase(String name, int mode, CursorFactory factory)
SQLiteDatabase dbContext = openOrCreateDatabase(getFilesDir() + "/test.db", MODE_PRIVATE, null);
// SQLiteDatabase 提供的 openOrCreateDatabase(String path, CursorFactory factory)
SQLiteDatabase dbSQLiteDatabase = SQLiteDatabase.openOrCreateDatabase(getFilesDir() + "/test.db", null);
第一个参数都是数据库的完整路径(包括数据库名,以 .db 为文件后缀),这里使用的 getFilesDir()
方法会得到路径为 /data/data/<package>/files/
的 File 对象,使用字符串拼接组成完整数据库路径。Context 提供的方法多了一个参数为操作模式,与 SharedPreferences 一样默认使用 MODE_PRIVATE
,其他模式均已弃用。最后一个参数都是 CursorFactory 对象,用于在查询数据时返回一个自定义的 Cursor,这里传入 null 即可。
SQLiteDatabase 中的方法主要有三类,以部分方法简单介绍。
- 管理类
openOrCreateDatabase()
:打开或创建数据库。isOpen()
:判断数据库是否已打开。close()
:关闭数据库。setVersion()
:设置数据库的版本号。
- 事务类
beginTransaction()
:开启事务。setTransactionSuccessful()
:设置事务成功标志。endTransaction()
:结束事务。
- 数据处理类
execSQL()
:执行拼接好的 SQL 控制语句。可以进行增删改操作, 但不能进行查询操作。insert()
:插入一条记录。delete()
:删除符合条件的记录。update()
:更新符合条件的记录。query()
:执行查询操作,返回结果集的游标。rawQuery()
:执行拼接好的 SQL 查询语句,返回结果集的游标。
SQLiteOpenHelper 数据库帮助类
实际开发中并不是直接使用 openOrCreateDatabase() 方法获取到一个 SQLiteDatabase 实例,然后再去进行各种操作的。而是通过 Android 提供的数据库辅助工具类 SQLiteOpenHelper,对 SQLite 数据库进行合理使用。创建 SQLiteOpenHelper 对象的时候,不会真正创建数据库或者连接数据库,调用 getReadableDatabase()
或 getWriteableDatabase()
才会触发该操作,同时数据库文件会保存到 /data/data/<package>/databases/
中。
使用步骤:
-
SQLiteOpenHelper 是一个抽象类,需要先创建一个继承自它的自定义类 DatabaseHelper,重写
onCreate()
和onUpgrade()
方法,并添加构造函数。public class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase db) { } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
-
上面是创建好自定义帮助类最开始的状态,接下来就要对构造函数进行修改。
public class DatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "amie.db"; private static final int DB_VERSION = 1; private static volatile DatabaseHelper instance = null; private DatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); } /** * 使用单例模式获取数据库帮助类唯一实例 * 一般来说,一个应用有一个数据库就够了,所以这里固定了数据库名,也可根据需求自定义参数 * 实际客户端并发量不像服务端那么大,可以不使用双重校验锁 */ public static DatabaseHelper getInstance(Context context) { if (instance == null) { synchronized (DatabaseHelper.class) { if (instance == null) { instance = new DatabaseHelper(context, DB_NAME, null, DB_VERSION); } } } return instance; } // ... }
-
添加保证数据库安全的必要方法。
想要在数据库中进行增删改查需要获取 SQLiteDatabase 对象,但实际上增删改查基于两种类型的锁(俗称读锁和写锁)。操作时应该使用具有对应锁的 SQLiteDatabase 对象,通过调用 DatabaseHelper 实例对象的
getReadableDatabase()
和getWritableDatabase()
方法可获取。public class DatabaseHelper extends SQLiteOpenHelper { private SQLiteDatabase readDB = null; private SQLiteDatabase writeDB = null; // ... /** 打开数据库的读连接 */ public SQLiteDatabase openReadLink() { if (readDB == null || ! readDB.isOpen()) { readDB = instance.getReadableDatabase(); } return readDB; } /** 打开数据库的写连接 */ public SQLiteDatabase openWriteLink() { if (writeDB == null || !writeDB.isOpen()) { writeDB = instance.getWritableDatabase(); } return writeDB; } /** 关闭数据库连接 */ public void closeLink() { if (readDB != null && readDB.isOpen()) { readDB.close(); readDB = null; } if (writeDB != null && writeDB.isOpen()) { writeDB.close(); writeDB = null; } } }
-
完成数据库初始化等逻辑(如:建表)。
public class DatabaseHelper extends SQLiteOpenHelper { // ... @Override public void onCreate(SQLiteDatabase db) { // 编写 SQL 建表语句 // SQLite 数据类型是一个用来指定任何对象的数据类型的属性。 String sql = "CREATE TABLE IF NOT EXISTS tb_user (" + " _id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," + " name TEXT NOT NULL," + " age INTEGER," + " gander INTEGER)"; // 执行语句完成建表 db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // SQLite 的局限性,只允许 重命名表 和 添加表字段,不支持 修改、删除表字段 和 添加约束。另外每次升级都只能做一个操作,如添加一个表字段,不可以添加多个。 // 升级数据库,一定要考虑以前的所有版本,对每个版本都进行适配,因为有可能跳版本升级。降级的时候,也要考虑所有的高版本,对每个高本版都进行适配。 // 假设新版本号为 3 if (oldVersion == 1) { // 既要添加 phone 字段还要添加 email 字段 String sql = "ALTER TABLE " + TABLE_USER + " ADD COLUMN phone TEXT"; db.execSQL(sql); sql = "ALTER TABLE " + TABLE_USER + " ADD COLUMN email TEXT"; db.execSQL(sql); } else if (oldVersion == 2) { // 只需要添加 email 字段 String sql = "ALTER TABLE " + TABLE_USER + " ADD COLUMN email TEXT"; db.execSQL(sql); } } }
-
提供对表记录进行增删改查等操作方法。
/** 与表对应的数据类,这里为了方便将属性设置为 public */ public class User { public Integer _id; public String name; public Integer age; public Integer gander; public User() {} public User(Integer _id, String name, Integer age, Integer gander) { this._id = _id; this.name = name; this.age = age; this.gander = gander; } }
public class DatabaseHelper extends SQLiteOpenHelper { // ... public long insertUser(User user) { ContentValues values = new ContentValues(); values.put("name", user.name); values.put("age", user.age); values.put("gander", user.gander); return writeDB.insert("tb_user", null, values); } public long deleteUserByName(String username) { // 第二个参数中的 ? 为占位符,对应第三个参数中对应位置下的值 return writeDB.delete("tb_user", "name = ?", new String[]{username}); // 要想删除所有行,将第二个参数置为 "1" 即可 // return writeDB.delete("tb_user", "1", null); } public long updateUserById(User user) { ContentValues values = new ContentValues(); values.put("name", user.name); values.put("age", user.age); values.put("gander", user.gander); return writeDB.update("tb_user", values, "_id = ?", new String[]{String.valueOf(user._id)}); } public List<User> queryAll() { List<User> list = new ArrayList<>(); // 按条件查询只需修改对应参数即可 Cursor cursor = readDB.query("tb_user", null, null, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { User user = new User(); user._id = cursor.getColumnIndex("_id"); user.name = cursor.getColumnIndex("name"); user.age = cursor.getColumnIndex("age"); user.gander = cursor.getColumnIndex("gander"); list.add(user); } } cursor.close(); return list; } }
-
在需要进行数据库操作的地方获取 DatabaseHelper 实例对象,开启读写连接,调用对应方法即可,别忘了关闭连接。
public class MainActivity extends AppCompatActivity { private DatabaseHelper databaseHelper; private void amie() { // 某个方法体中 User user = new User(1, "Vastness", 22, 1); long rowID = databaseHelper.insertUser(user); databaseHelper.updateUserById(user); databaseHelper.deleteUserByName("Amie"); databaseHelper.queryAll(); } @Override protected void onStart() { super.onStart(); databaseHelper = DatabaseHelper.getInstance(this); databaseHelper.openWriteLink(); databaseHelper.openReadLink(); } @Override protected void onStop() { super.onStop(); databaseHelper.closeLink(); } }
事务管理
以连续插入多条数据为例,要求都成功时才插入,只有有一条插入失败,就全都不插入。
try {
writeDB.beginTransaction();
writeDB.insert(TABLE_USER, null, values);
writeDB.insert(TABLE_USER, null, values);
writeDB.insert(TABLE_USER, null, values);
writeDB.setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
writeDB.endTransaction();
}