探索SquiDB:新一代的跨平台SQLite数据库层
项目简介
SquiDB是一个专为Android和iOS设计的强大的SQLite数据库库,它的目标是让开发者在处理数据库时既能保持代码的简洁性,又能充分利用SQL的强大功能。它将类型安全的对象与面向对象的SQL语句构建器相结合,避免了繁琐的SQL字符串操作,同时也提供了一套工具,帮助轻松进行数据库迁移以及实现ContentProviders。
如果你有任何问题或需要支持,请加入我们的Gitter聊天室:
快速入门
要在你的build.gradle
文件中添加SquiDB依赖项,只需以下几步:
// 此示例适用于典型的Android设置,对于j2objc/iOS设置可能会有所不同
buildscript {
repositories {
jcenter()
}
dependencies {
// android-apt 插件; 只需当使用android gradle插件版本小于2.2.0时引入
// classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
repositories {
jcenter()
}
// 当使用android gradle插件版本小于2.2.0时才需要
// apply plugin: 'com.neenbedankt.android-apt'
// 如果使用Kotlin,你可能需要使用这个插件代替默认的annotationProcessor配置
// apply plugin: 'kotlin-kapt'
dependencies {
compile 'com.yahoo.squidb:squidb:3.2.3'
compile 'com.yahoo.squidb:squidb-annotations:3.2.3'
compile 'com.yahoo.squidb:squidb-android:3.2.3' // 仅适用于Android项目
annotationProcessor 'com.yahoo.squidb:squidb-processor:3.2.3'
// 如果使用android-apt插件,这变成
// apt 'com.yahoo.squidb:squidb-processor:3.2.3'
// 如果使用Kotlin语言,这变成
// kapt 'com.yahoo.squidb:squidb-processor:3.2.3'
}
更多关于添加SquiDB依赖的详细信息,可以参考这个wiki页面。
跨平台支持
从SquiDB 3.0版本开始,通过Google的j2objc工具支持跨平台开发。这意味着你可以利用SquiDB作为SQLite数据层来编写可运行在Android和iOS两个平台的业务逻辑。如果你不需要这一特性,完全可以忽略,SquiDB依然会在Android平台上像以前一样工作,且只需进行一些小而简单的API更新。
模型对象
SquiDB通过编译时代码生成让你以最小的努力定义表结构(类似于ORM)。一个SquidDatabase对象负责从数据库读取和写入这些对象。设置所有组件快速且简单。例如:
// 这是一个表格模型
@TableModelSpec(className = "Person", tableName = "people")
class PersonSpec {
// 文本列,名为 "firstName"
String firstName;
// 文本列,名为 "lastName"
String lastName;
// 长整数列,名为 "creationDate",但在模型中称为 "birthday"
@ColumnSpec(name = "creationDate")
long birthday;
}
// 这是你如何创建数据库实例的方式
public class MyDatabase extends SquidDatabase {
private static final int VERSION = 1;
public MyDatabase() {
super();
// 其他初始化实例的操作
}
@Override
public String getName() {
return "my-database.db";
}
@Override
protected Table[] getTables() {
return new Table[]{
// 列出所有表
Person.TABLE,
};
}
@Override
protected int getVersion() {
return VERSION;
}
// 其他可重写的方法存在用于迁移和初始化;这里为了简洁省略
}
MyDatabase db = new MyDatabase(); // 重要:db实例应该始终是单例
// 这是如何与生成的模型交互
Person newPerson = new Person()
.setFirstName("Sam")
.setLastName("Bosley")
.setBirthday(System.currentTimeMillis());
db.persist(newPerson);
...
String firstName = newPerson.getFirstName();
String lastName = newPerson.getLastName();
long birthday = newPerson.getBirthday();
构建查询
除了为所有列定义getter和setter之外,生成的模型类还定义了常量字段,可用于构造查询:
long ageCutoff = System.currentTimeMillis() - (DateUtil.YEAR_IN_MILLIS * 18);
Query peopleWhoCanVote = Query.select().where(Person.BIRTHDAY.lt(ageCutoff));
// 这相当于: select * from people where people.birthday < ?
// 其中的问号代表年龄限制参数
SquidCursor<Person> voters = db.query(Person.class, peopleWhoCanVote);
例子很简单,但SquiDB的Query对象几乎支持完整的SQL语法,尤其对于复杂的查询,其代码更清晰,易于维护:
String sql = "select " + PersonColumns.AGE + ", " + ProfileImageColumns.URL + " from "
+ PERSON_TABLE + " left join " + PROFILE_IMAGE_TABLE + " on " + PersonColumns._ID
+ " = " + ProfileImageColumns.PERSON_ID + " where " + PersonColumns.NAME + " = ?"
+ " AND " + PersonColumns.AGE + " >= ?" + " ORDER BY " + PersonColumns.AGE + " ASC"
String[] sqlArgs = new String[]{"Sam", Integer.toString(18)};
// 相当于...
Query query = Query.select(Person.AGE, ProfileImage.URL).from(Person.TABLE)
.leftJoin(ProfileImage.TABLE, Person.ID.eq(ProfileImage.PERSON_ID))
.where(Person.NAME.eq("Sam").and(Person.AGE.gte(18)));
使用字符串构建查询时,需要使用'?'字符作为占位符来表示参数,这对Android的SQLiteDatabase用户来说很熟悉。虽然这是一个好习惯,但它使代码难以阅读,需要额外的字符串数组存储参数。SquiDB在构建Query对象时插入这些占位符,并自动绑定参数。使用原始SQL版本容易出现错误,例如在更新SQL时添加、删除或更改sqlArgs的内容。对于大型复杂查询来说,这个问题更为明显。使用SquiDB的Query对象可以避免这类问题,减少因拼写错误导致的问题。
此外,基于对象构建和组合查询或SQL子句变得更简单:
public Query queryForPeopleWithName(String name, boolean includeLastName) {
Query baseQuery = Query.select().from(Person.TABLE);
Criterion nameCriterion = Person.FIRST_NAME.eq(name);
if (includeLastName) {
nameCriterion = nameCriterion.or(Person.LAST_NAME.eq(name));
}
baseQuery.where(nameCriterion);
return baseQuery;
}
查询结果处理
SquidDatabase可以直接返回由模型对象表示的单行数据,或者由模型类型参数化的SquidCursor:
// 获取_id = 1的人
Person person1 = db.fetch(Person.class, 1);
// 包含people表的所有行的cursor
SquidCursor<Person> personCursor = db.query(Person.class, Query.select());
模型对象设计为可复用的,因此在循环中通过cursor重新加载模型对象以进行操作是非常便宜的,除非你需要在循环之外保留行数据:
SquidCursor<Person> personCursor = db.query(Person.class, Query.select());
try {
Person person = new Person();
while (personCursor.moveToNext()) {
person.readPropertiesFromCursor(personCursor);
doSomethingWithCurrentRow(person);
}
} finally {
personCursor.close();
}
SquidCursor也提供了类型安全的get()方法,可以直接处理表列,如果不需要完全加载模型对象,这是非常方便的:
String firstName = personCursor.get(Person.FIRST_NAME);
Long birthday = personCursor.get(Person.BIRTHDAY);
即使跨越多个表进行操作,处理模型对象也非常简单。
SquidCursor与Android Cursor
SquidCursor实现了对Android Cursor接口的重新声明,所以你可以在与标准Android cursor相同的方式下使用它——方法如moveToFirst()
, moveToNext()
, isAfterLast()
等都与标准的Android cursor行为一致。若在Android应用中需要一个实际的android.database.Cursor
实例,你可以这样做:
SquidCursor<Person> myCursor = ...;
Cursor androidCursor = (Cursor) myCursor.getCursor();
数据变更通知
SquiDB支持监听数据库变化并在写操作后发送通知或回调。通知机制非常灵活,可以通过用户自定义的DataChangedNotifier
子类进行定制。DataChangedNotifier
对象基于写操作或事务期间的元数据积累通知(例如哪些表已更改,或者哪一行已被更新)。只有当操作或事务成功完成时,才会发送这些通知。
提供了几种常见用例的DataChangedNotifier
实现:
UriNotifier
支持向Uri发送通知,以便与Android的ContentObserver机制一起使用。这对于使用SquiDB实现ContentProvider很有帮助。SimpleDataChangedNotifier
支持在成功写入所监听的表或表后执行任意回调。- 对于偏好响应式架构的开发者,可以使用
squidb-reactive
库。
SquiDB是一个强大且易用的框架,可以帮助开发者高效地管理SQLite数据库,无论是在单一平台还是跨平台的应用中。现在就尝试一下,看看它能为你的项目带来什么改变吧!