0%

Kotlin 支持

kotlin Entity

  • 在 kotlin 中,ID 属性应该这样定义 @Id var id: Long = 0.ID 必须是 var.

构造器

  • ObjectBox 优先调用全参的构造方法。如果自定义属性或 transient 属性 或关联属性是构造方法的一部分参数,ObjectBox 将不会调用此构造方法.所以应该提供为这些参数提供默认值以确保无参构造方法存在。
1
2
3
4
5
6
7
@Entity
data class Note(
@Id var id: Long = 0,
val text: String = "",
@Convert( converter = StringsConverter::class, dbType = String::class)
val strings: List<String> = listOf()
)

kotlin Entity 中定义关联属性

在 kotlin 中定义关联属性可能比较麻烦。但请注意:关联属性必须为 var. 否则 initialization magic 将不起作用.
通常可以使用 lateinit 修饰关联属性

1
2
3
4
5
6
7
8
9
10
11
12
@Entity
class Order {
@Id var id: Long = 0
lateinit var customer: ToOne<Customer>
}

@Entity
class Customer {
@Id var id: Long = 0
@Backlink( to = "customer")
latelinit var orders: List<Order>
}

kotlin 扩展函数

1
2
3
dependencies {
implementation "io.objectbox:objectbox-kotlin:$objectboxVersion"
}

kotlin

1
2
3
4
5
6
7
8
9
10
11
12
13
val box: Box<DataClassEntity> = store.boxFor()

val query = box.query {
equal(property,value)
order(property)
}

val query = box.query().inValues(property,array).build()

toMany.applyChangesToDb(resetFirst = true) { // 默认 false
add(entity)
removeById(id)
}

Java 桌面应用

嵌入式数据库

ObjectBox 不仅仅适用于 Android 项目,同时也适用于运行在 Windows Linux macOS 上的纯 Java(JVM) 桌面应用.

配置

请使用 Gradle 作为构建工具,因为 ObjectBox 使用了 Gradle 插件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
buildscript {
ext.objectboxVersion = '2.3.3'
repositories {
jcenter()
maven{ url "https://plugins.gradle.org/m2/"}
}
dependencies {
classpath "net.ltgt.gradle:gradle-apt-plugin:0.20"
classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
}
}
repositories {
jcenter()
}
apply plugin: 'java'
apply plugin: 'net.ltgt.apt-idea' // 注解处理器插件
apply plugin: 'io.objectbox'

Native 库

ObjectBox 是由 C/C++写成的可以运行大多数 native code 的对象数据库。

改变 Model 文件位置

默认 model 文件存储在 module-name/objectbox-models/default.json.可以通过修改 objectbox.modelPath 来改变

1
2
3
4
// 在项目 build.gradle 文件, apply plugin: 'java' 之后添加
tasks.withType(JavaCompile) {
options.compilerArgs += ["-Aobjectbox.modelPath=$projectDir/schemas/object.json]
}

改变 MyObjectBox 包名

1
2
3
tasks.withType(JavaCompile) {
options.compilerArgs += [ "-Aobjectbox.modelPath=$projectDir/schemas/objectbox.json" ]
}

开启 debug 模式

1
2
3
4
5
6
7
8
// enable debug output for plugin
objectbox {
debug true
}
// enable debug output for annotation processor
tasks.withType(JavaCompile) {
options.compilerArgs += [ "-Aobjectbox.debug=true" ]
}

可以使用 BoxStore builder 的 name(String) 来改变数据库存储的位置。

单元测试

添加 junit 4 库

LiveData (Arch.Comp.)

从 1.2.0 开始支持 Android Architecture Components
ObjectBox 提供 ObjectBoxLiveData 可以在 ViewModel 中使用

1
2
3
4
5
6
7
8
9
10
11
public class NoteViewModel extends ViewModel {
private ObjectBoxLiveData<Note> noteLiveData;

public ObjectBoxLiveData<Note> getNoteLiveData(Box<Note> notesBox) {
if (noteLiveData == null) {
// 查询所有的 notes, text 按 a-z 的顺序排列
noteLiveData = new ObjectBoxLiveData(notesBox.query().order(Note_.text).build());
}
return noteLiveData;
}
}
  • 上一种方法需要传入 Box.可以使用 AndroidViewModel 代替,它可以访问 Application context,然后会在 ViewModel 中调用 ((App)getApplication()).getBoxStore().boxFor().第一种的优势在于没有引用 Android 类,所以可以进行单元测试。
1
2
3
4
5
6
7
NoteViewModel model = ViewModelProviders.of(this).get(NoteViewModel.class);
model.getNoteLiveData(notesBox).observe(this,new Observer<List<Note>>{
@Override
public void onChanged(@Nullable List<Node> notes) {
notesAdapter.setNotes(notes);
}
})

Paging (Arch.Comp.)

从 2.0.0 开始支持.ObjectBox 提供了 ObjectBoxDataSource 类.它继承了 paging 库的 PositionalDataSource
在 ViewModel 中,类似创建 LiveData,先创建 ObjectBox query.然后构造并使用 ObjectBoxDataSource 工厂代替 LiveData.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NotePageViewModel extends ViewModel {
private LiveData<PagedList<Note>> noteLiveDataPaged;

public LiveData<PagedList<Note>> getNoteLiveDataPaged(Box<Note> notesBox) {
if(noteLiveDataPaged == null) {
Query<Note> query = notesBox.query().order(Note_.text).build();
noteLiveDataPaged = new LivePagedListBuilder(
new ObjectBoxDataSource.Factory(query),
20 // 页数
).build();
}
return noteLiveDataPaged;
}
}

安卓本地单元测试

设置测试环境

| 此配置仅针对 ObjectBox 1.4 及之前版本.新版本已经自动添加了 native ObjectBox 依赖库。

1
2
3
4
5
6
7
8
9
// /app/build.gradle
depdendencies {
// 必备 JUnit 4
testImplementation 'junit:unit:4.12'
// 手动添加平台独立的 native Objectbox 依赖库.(可选)
testImplementation "io.objectbox:objectbox-linux:$objectboxVersion"
testImplementation "io.objectbox:objectbox-macos:$objectboxVersion"
testImplementation "io.objectbox:objectbox-windows:$objectboxVersion"
}

| 本地单元测试仅支持 64 位系统.
| windows 可能需要安装 Microsoft Visual C++ 2015 Redistributable(x64)

创建本地单元测试类

  • 可以使用 BoxStore builder 的directory(File) 指定数据库保存在本地设备上。
  • 为保证数据不交叉污染,可以使用 BoxStore.deleteAllFiles(File) 删除已经存在的数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class NoteTest {
private static final File TEST_DIR = new File("objectbox-example/test-db");
private BoxStore store;

@Before
public void setUp() throws Exception {
// 删除之前的数据库
BoxStore.deleteAllFiles(TEST_DIR);
store = MyObjectBox.builder()
// 指定数据库存放路径
.directory(TEST_DIR)
// 添加 debug 标记打印日志
.debugFlags(DebugFlags.LOG_QUERIES | DebugFlags.LOG_QUERY_PARAMETERS)
.build();
}

@After
public void tearDown() throws Exception {
if (store != null) {
store.close();
store = null
}
BoxStore.deleteAllFiles(TEST_DIR);
}

@Test
public void exampleTest() {
Box<Note> noteBox = store.boxFor(Note.class);
assertEquals(...);
}
}

关系测试

  • ObjectBox 1.4.4 及之后
  • 为了测试具有 ToOne,ToMany 属性的 entity,必须在本地 JVM 初始化 entity 并且添加一个 transient 的 BoxStore 属性.

Entity 注解

entity 数据可访问

  • ObjectBox 需要访问 entity 的属性(生成 Cursor 类).
    • 属性包内可见。kotlin 中使用 JvmField
    • 提供标准 getters
  • 为了提升性能,请提供具有全部属性的构造方法
1
2
3
4
5
6
7
8
9
10
11
12
@Entity
public class User {
@Id private long id;
private String name;
@Transient private int tempUsageCount;
public User(){/* 默认构造方法 */}
public User(id,name){
this.id = id;
this.name = name;
}
// getters and setters for properties...
}

entity 属性的注解

  • @NameInDb 可以在数据库中为属性命名。
    • 应该使用 @Uid 注解来代替重命名属性和 entity
    • @NameInDb 仅支持内联常量来指定列名。
  • @Transient 保证属性不被持久化,transient,static 修饰符也一样.

属性索引 @Index

  • @Index 当前不支持 byte[] float double
  • Index typs(String).
    • ObjectBox 2.0 引入了 index types.之前对每一个索引,使用属性的值来完成查询。
      现在 ObjectBox 可以使用 hash 来生成 index.
      由于 String 属性明显比标量值更占空间,ObjectBox 对 strings 使用默认的 index type 完成 hash.
      可以针对 String 类型的属性明确指定 index type 为基于值构建索引。
      1
      2
      @Index( type = IndexType.VALUE)
      private String name;
    • 请注意:对于 String 类型的属性,基于值的索引可能比默认基于 hash 的索引更占空间,这种结论取决于该值的长度。
    • ObjectBox 支持以下索引属性:
      • 未指定或默认: 根据属性的类型决定(HASH for String,VALUE for others)
      • VALUE: 使用属性值生成索引。例如 String,可能更占空间
      • HASH: 使用属性值的 32 位 hash 生成索引。偶尔可能发生 hash 碰撞,但实际概率很小。通常比 HASH64 更佳,因为占空间小
      • HASH64: 使用属性值的长 hash 生成索引。比 HASH 占空间大,所以一般情况下不是首选.

Unique 约束

如果 unique 约束的属性值冲突,put() 操作将被终止且抛出 UniqueViolationException 的异常.
Unique 基于 Index,所以可以同时给属性添加 @Index 注解。

关系

Entity

在一个类中至少需要 @Entity@Id 两个注解才能定义一个 ObjectBox model.例如:

1
2
3
4
5
6
// User.java
@Entity
public class User {
@Id public long id;
public String name;
}

然后 make 就可生成 model.

| @Id 的类型必须为 long.
| 如果 entity 发生了很大的变动(如移动类或修改注解),必须 rebuild 项目以便 ObjectBox 生成的代码得到更新.

核心类

  • MyObjectBox: 基于 entity 类生成。提供 builder 配置 BoxStore.
  • BoxStore: ObjectBox 的入口。操作数据库,管理 Boxes 的工具.
  • Box: 对 entity 保存和查询。每一个 entity 都有一个对应的 Box(由 BoxStore 提供).

核心初始化

实例化 BoxStore 的最好时机是在 app 启动时。推荐在 Application 类的 onCreate 方法中进行.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class App extends Application {
private BoxStore boxStore;

@Override
public void onCreate() {
super.onCreate();
boxStore = MyObjectBox.builder().androidContext(this).build();
}

public BoxStore getBoxStore() { return boxStore; }
}
// 然后在 app 生命周期内就可以使用了
notesBox = ((App) getApplicationi()).getBoxStore().boxFor(User.class);

Box 基本操作

  • put: 存入一个对象,可能会覆盖具有相同 ID 的对象。即使用 put 插入或更新对象。返回 ID。
  • get,getAll: 提供一个对象的 ID,可快速的通过 get获取它。getAll 获取指定的所有的对象。
  • remove,removeAll: 从 box 中删除一个对象。removeAll 删除指定的所有对象.
  • count: 返回该 box 存储的对象数量.
  • query: 返回一个 query builder.

Object IDs

  • Entity 必须具有一个类型为 long,由 @Id 注解属性。当然可以使用可空的 java.lang.Long类型,但不推荐。
    如果需要使用另外一种类型的 ID(服务器返回的 String 类型的 UID),把它作为普通属性即可,然后可以通过此 ID 查询.

    1
    2
    3
    4
    5
    6
    @Entity class StringIdEntity {
    @Id priavte long id;
    private String uid;
    }

    StringIdEntity entity = box.query().equal(StringIdEntity_.uid,uid).findUnique();
  • 指定 ID
    ObjectBox 默认会为新对象指定 IDs.ID 自增.

    1
    2
    3
    4
    5
    User user = new User();
    // user.id == 0
    box.put(user);
    // user.id != 0
    long id = user.id

    如果插入的对象的 ID 比 box 里的 ID 最大值还大,ObjectBox 将抛出错误.

  • 保留 Object IDs
    Object IDs 不能:
    – 0,null(使用 java.lang.Long)。
    – 0xFFFFFFFFFFFFFFFF(java 中的 -1):内部保留

事务

  • put 运行在隐式事务中
  • 优先使用 put 批量操作列表 (put(entities)
  • 如果在循环中操作大量数据,考虑明确使用事务,如runInTx()

关于

ObjectBox 定位是: 针对移动端和 IoT 超快的 (superfast edge database) 面向对象的数据库 .为小型设备提供了边缘计算能力,使得数据可以快速高效地在本地存储、处理、安全管理.ObjectBox 小于 1MB,最适合移动 APP、小型 IoT 设备及路由。并且 ObjectBox 也是第一个在边缘设备上兼容 ACID 的高性能的 NoSQL 数据库.所有的产品都是基于工程师思维开发的,所以可以使用最少的代码去实现想要的功能。

优点

  • 比同类竟品快 10 倍以上。BenchMark
  • 跨平台。支持 Linux、Windows、Mac/iOS、Android、Raspberry Pi、ARM 等嵌入式设备和容器。
  • 小于 1MB,特别针对小型设备设计和优化。
  • 易使用。
  • 支持 reactive.
  • 无缝结合 greenDAO.(同一家公司出品)
  • 更好地支持关系型数据. 提供了改变追踪(change tracking),级联添加(cascading puts),灵活的加载策略(eager,lazy)
  • 无需掌握 SQL:ObjectBox 设计简单,使用方便,不需要掌握 SQL 即可上手.
  • 支持 kotlin: 包括 data class.

使用

Android(Java)

1
2
3
4
5
6
7
8
9
10
11
12
// root 目录 build.gradle
buildscript {
ext.objectboxVersion = '2.3.3'
repositories {
jcenter()
}
dependencies {
// Android Gradle plugin 最低版本为 3.0.0
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
}
}
1
2
3
// app 或其他 module build.gradle
apply plugin: 'com.android.application'
apply plugin: 'io.objectbox' 放在最下面

Android(Kotlin)

1
2
3
4
5
// app 或其他 module build.gradle
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt' 如果使用 kotlin-android 插件,必须加 kotlin -kapt 插件
apply plugin: 'io.objectbox' 放在最下面

Sync gradle 即可自动添加 ObjectBox 依赖.

配置

1、首先添加 ObjectBox 插件.
2、如果 ObjectBox 插件没有自动添加依赖库和注解处理器,请手动添加依赖。

1
2
3
4
5
6
// Android(Java)
// /app/build.gradle
dependencies {
compile "io.objectbox:objectbox-androoid:$objectboxVersion"
annotationProcessor "io.objectbox:objectbox-processor:$objectboxVersion"
}
1
2
3
4
5
6
7
8
// Android(kotlin)
// /app/build.gradle
dependencies {
compile "io.objectbox:objectbox-android:$objectboxVersion"
kapt "io.objectbox:objectbox-processor:$objectboxVersioni"
// 针对 kotlin 的扩展函数(可选)
compile "io.objectbox:objectbox-kotlin:$objectboxVersion"
}

3、改变 Model 文件的路径

ObjectBox Model 文件默认保存在 module-name/objectbox-models/default.json

1
2
3
4
5
6
7
8
9
10
11
// Android(Java)
// /app/build.gradle
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = ["objectbox.modelPath":"$projectDir/schemas/objectbox.json".toString()]
}
}
}
}
1
2
3
4
5
6
7
// Android(Kotlin)
// /app/build.gradle
kapt {
arguments {
arg("objectbox.modelPath":"$projectDir/schemas/objectbox.json")
}
}

4、改变 MyObjectBox 的包名

MyObjectBox 类的包名默认和 entitiy 类的包名或其上一级报名一致。

1
2
3
4
5
6
7
8
9
10
11
// Android(Java)
// /app/build.gradle
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = ["objectbox.myObjectBoxPackage":"com.example.custom"]
}
}
}
}
1
2
3
4
5
6
7
// Android(Kotlin)
// /app/build.gradle
kapt {
arguments {
arg("objectbox.myObjectBoxPackage", "com.example.custom")
}
}

5、开启 Debug 模式

在 /app/build.gradle 中添加必要的选项后,运行 ./gradlew --info 即可查看 debug 输出

1
2
3
4
5
6
7
8
9
10
11
// Android(Java)
// /app/build.gradle
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = ['objectbox.debug' : 'true']
}
}
}
}
1
2
3
4
5
6
7
// Android(Kotlin)
// /app/build.gradle
kapt {
arguments {
arg("objectbox.debug", true)
}
}

6、开启 DaoCompat 兼容模式

从 greenDAO 迁移过来,生成和 greenDAO 相似的 API,使 ObjectBox 看起来就像 SQLite 一样。

1
2
3
4
// /app[module]/build.gradle
depdendencies {
compile "org.greenrobot:objectbox-daocompat:1.10"
}

然后开启 DaoCompat 模式

1
2
3
4
5
6
7
8
9
10
11
// Android(Java)
// /app[module]/build.gradle
android {
defaultConfig {
javaCOmpileOptions {
annotationProcessorOptions {
arguments = ['objectbox.daoCompat':'true']
}
}
}
}
1
2
3
4
5
6
7
// Android(Kotlin)
// /app[module]/build.gradle
kapt {
arguments {
arg("objectbox.daoCompat":true)
}
}

如果你计划从 greenDAO 迁移到 ObjectBox,那么你可能会保留原来的 greenDAO entity 类(复制这些类到另外的包中)然后按如下修改。

  • 首先改变注解。请注意:不是所有的 greenDAO 注解都支持无缝迁移到 ObjectBox,支持的如下:
1
2
3
4
5
6
7
// greenDAO
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation...

// ObjectBox
import io.objectbox.annotation.Entity;
import io.objectbox.annotation...
  • ObjectBox 当前不支持 unique indexes,naming indexes,或者在多个属性间 indexes.
1
2
3
4
5
6
7
// greenDAO
@Entity(indexes = ...)
@Index(name = "idx1", unique = true) private String name;
@Unqiue private String name;

// ObjectBox
@Index private String name;
  • 自定义类型。修改父类,当然也可同时继承,这样该自定义类型就可同时在 greenDAO 和 ObjectBox 间使用
1
2
3
4
5
// greenDAO
import org.greenrobot.greendao.converter.PropertyConverter;

// ObjectBox
import io.objectbox.converter.PropertyConverter;

修改 @Convert 注解里的 columnType 改为 dbType

1
2
3
4
5
// greenDAO
@Convert(converter = NoteTypeConverter.class, columnType = String.class)

// ObjectBox
@Convert(converter = NoteTypeConverter.class, dbType = String.class)
  • 关系。ObjectBox 使用 ToOneToMany类型替代 greenDAO 的 @ToOne@ToMany 注解。
  • 使用 BoxStore.
    修改完 entity 后,设置 BoxStore 创建 DaoSession.
1
2
3
4
5
6
7
8
9
// 通常在 Application 类中
boxStore = MyObjectBox.builder().androidContext(this).build();
daoCompatSession = new DaoSession(boxStore);
// 在迁移完成后,你可能想移除这些迁移操作。
// 那么通过 greenDAO session 获取 entities,把他们转为 ObjectBox entities,
// 然后使用 DaoCompat session 插入。
List<com.example.app.daos.greendao.Note> notes = daoSession.getNoteDao().loadAll();
List<Note> convertedNotes = convertToObjectBoxNotes(notes);
daoCompatSession.getNoteDao().insertInTx(convertedNotes);

默认没有设置 ID (即 id == 0),ObjectBox 会为插入的数据生成一个新的 ID.如果想保留原来的 ID,请修改 @Id(assignable = true)

  • 使用 DaoCompat DaoSession
    在使用新 compat session 替换原来的 API 后,可以通过在 Application 类中的一个方法返回 DaoSession
1
2
3
4
5
6
public DaoSession getDaoSession() {
// greenDAO
// return daoSession;
// ObjectBox
return daoCompatSession;
}

表面上 compat DaoSession 是 greenDAO DaoSession 的替代品,其实它内部是使用 BoxStore 代替了 greenDAO 数据库.
如果还使用了 greenDAO 的额外特性,比如 queries,那么还需如下修改:

1
2
3
4
5
6
// greenDAO
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao...
// ObjectBox
import org.greenrobot.daocompat.query.Query;
import org.greenrobot.daocompat...
  • Queries
    DaoCompat 支持以下的 Query 功能:
    remove() 替代 DeleteQuery
    count() 替代 CountQuery
    – 不支持 CursorQuery

  • DaoCompat 和 greenDAO 的不同
    – 不支持 NotNull
    – 不支持 Joins原生 SQL 查询.
    – 不支持异步 sessions: startAsyncSession()
    – 不支持加密
    – 仅支持简单的 AbstractDaoTestAbstractDaoBasicTest