ObjectBox是一个专门为物联网和移动设备打造出的非常快速的面向对象的数据库,它有一下几个特点
在根目录的build.gradle
,添加
buildscript {
ext.objectboxVersion = '2.3.3'
respositories {
jcenter()
}
dependencies {
// Android Gradle Plugin 3.0.0 or later supported
classpath 'com.android.tools.build:gradle:3.3.1'
classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
}
}
在APP或者module的build.gradle
加入
apply plugin: 'com.android.application'
apply plugin: 'io.objectbox' // apply last
然后在你的Application中初始化
public class MyApplication extends Application {
private static MyApplication mApplication;
private BoxStore boxStore;
@Override
public void onCreate() {
super.onCreate();
mApplication = this;
initDB();
}
private void initDB(){
boxStore=MyObjectBox.builder().androidContext(mApplication.getApplicationContext()).build();
AccountManager.getInstance().setCurrentAccountFromDB();
}
public static MyApplication getMyApplication() {
return mApplication;
}
public BoxStore getBoxStore() {
return boxStore;
}
}
这个相信使用过ORM类型数据库的都知道的
@Entity
public class Student{
@Id public long id;
public String name;
public int grade;
public int classId;
}
java.lang.Long
,但是官方不推荐使用。默认的情况下,一旦entity持久化,Objectbox就会分配一个ID给这个实体,可以不用管。(Tips:我们可以通过Id是不是0来判断,是否这个实体持久化,Id只有两个值是意外的:0和-1,0代表了为持久化,-1只在代码内部使用)@Id(assignable = true)
long id;
这个允许我们赋值任何有效的Id(除0,-1),赋值0就意味着让ObjectBox赋值
2. 其他类型值
ObjectBox获取实体属性的数据,不用加注解,但是需要保证下面两种情况之一
@Entity
public class Student {
@Id
private long id;
@NameInDb("NAME")
private String name;
@NameInDb("GRADE")
private int grade;
@Index
private int classId;
@Transient
private String className;
private static int dormitoryId;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public int getClassId() {
return classId;
}
public void setClassId(int classId) {
this.classId = classId;
}
}
<1> @NameInDb("NAME")
这个定义在数据库当中该属性的名称,定义这个之后,后面更改Java层面的属性名并不影响数据库
<2> @Transient
和序列化的时候一样,标记这个属性不会被持久化
<3> static
属性也不会被持久化
<4> @Index
为对应的数据库列创建数据库索引(注:目前不支持byte[],float和double),这里作者还引入了Index type索引值的概念,之前的Index使用的都是属性值,现在可以对属性值进行 hash来构建index,比如String类型,因为作者觉得挺耗费空间
@Index(type = IndexType.VALUE)
private String name;
这里的IndexType有四个值:
@Unique
表明在数据库当中,这个属性的值为唯一,否则抛出UniqueViolationException
这个关系定义为:对象指向其他的对象,他们之间就叫有关系,有两种关系To one ,To many,这里需要注意两个概念source object定义关系的对象,target object被引用的关系对象
一对一关系中:我们有三个方法来设置关系:
setTarget(entity)
让entity成为新的target,也可以是在数据库中已经存在的,null就是清除关系
setTargetId(entityId)
这个Id是数据库中已经存在的实体Id,0代表清除关系
setAndPutTarget(entity)
让entity成为新的target,并且把entity放进数据库中,如果原对象之前不在数据库中,在调用setAndPutTarget()之前需要调用attach
Son son=new Son();
sonBox.attach(order);//需要填写
son.customer.setAndPutTarget(father);
//Father.java 具体使用案例
@Entity
public class Father{
@Id public long id;
}
//Son.java
@Entity
public class Son{
@Id public long id;
public ToOne father;
}
//在代码中如何添加
Father father=new Father();
Son son=new Son();
son.father.setTarget(father);
long sonId=boxStore.boxFor(Son.class).put(son);
//在代码中如何获取
Son son=boxStore.boxFor(Son.class).get(sonId);
Father father=son.father.getTarget(father);
如果Father对象不在数据库当中,那么就会把Father放进去,如果存在,就只是建立一个之间的关系
但是如果我们的**target对象的Id是自己赋值的即@Id(assignable =true)
**那么就不会自动插入,需要我们自己手动执行如下代码
fatherBox=boxStore.boxFor(Father.class);
sonBox=boxStore.boxFor(Son.class);
father.id=110;
fatherBox.put(father);
son.father.setTarget(father);
sonBox.put(son);
我们也可以废除之间的关系,但是并不会在数据库中删除掉对应的target对象
son.father.setTarget(null);
sonBox.put(son);
ToOne关系的实现原理
我们可以查看我们项目的objectbox-models/default.json
看到其实是在Son类里面增加了一个fatherId的属性,可以通过@TargetIdProperty(String),修改这个名称。如下所示:
@Entity
public class Son {
@Id
public long id;
@TargetIdProperty("son_father_id")
public ToOne father;
}
提高性能的技巧
如何提高性能?首先我们需要明白,我们使用ToOne
的father对象的时候,代码里面是没有初始化这段代码的,son.father.setTarget(father);
比如这一句,并没有报NullPointerException错误,因为在代码执行前,ObjectBox Gradle 插件在构造函数的时候,添加了初始化这个target entity的初始化工作。
为了提高性能,我们应该提供一个全属性的构造函数(包含target的Id,这个隐含属性可以在工程目录的object-models/defaoult.json
里面找到)
To-Many关系里面两种:一种是one-to-many,一种是many-many
(对于To-many关系,在第一次请求的时候会被延迟的执行,然后缓存到to-many的source对象里面去,所以,对关系的get方法后续不会从数据库中查询到)
//Father.java
@Entity
public class Father{
@Id public long id;
@Backlink(to = "father")
public ToMany sons;
}
//son.java
@Entity
public class Son{
@Id public long id;
public ToOne father;
}
//如何添加
Father father=new Father();
father.sons.add(new Son());
father.sons.add(new Son());
long fatherId=boxStore.boxFor(Father.class).put(father);
添加的规则也一样,如果@Id(assignable = true
,那么不会自动插入,需要在添加之前执行,ObjectBox只放被引用对象ID为0的实体
//如果是source entity使用 @Id(assignable = true
father.id=12;
fatherBox.attach(father);
father.sons.add(son);
fatherBox.put(father);
//如果是target entity使用
son.id=12;
sonBox.put(son);
father.sons.add(son);
fatherBox.put(son);
//get
Father father=fatherBox.boxFor(Father.class).get(fatherId);
for(Son son: father.sons){
//TODO ....
}
//Remove
Son son=father.orders.remove();
//参数既可以是son,也可以是数字
使用注解ToMany
@Entity
public class Teacher{
@Id public long id;
}
@Entity
publci class Student{
@Id publci long id;
public ToMany teachers;
}
//添加数据
Teacher teacher1 = new Teacher();
Teacher teacher2 = new Teacher();
Student student1 = new Student();
student1.teachers.add(teacher1);
student1.teachers.add(teacher2);
Student student2 = new Student();
student2.teachers.add(teacher1);
student2.teachers.add(teacher2);
boxStore.boxFor(Student.class).put(student1,student2);
//get
Student student1 = boxStore.boxFor(Student.class).get(student1.id);
for(Teacher teacher : student1.teachers){
//TODO
}
//remove
student1.teachers.remove(0);
student1.teachers.applyChangesToDb();
如果想知道,一个老师有什么学生,可以写
// Teacher.java
@Entity
public class Teacher{
@Id public long id;
@Backlink(to = "teachers") // backed by the to-many relation in Student
public ToMany students;
}
// Student.java
@Entity
public class Student{
@Id public long id;
public ToMany teachers;
}
因为使用的是非关系型数据库,所以这里的查询通过构建一个QueryBuilder操作,下面只是一个小例子,具体的API可以看下源码
QueryBuilder builder = userBox.query();
builder.equal(User_.firstName,"Joe)
.greater(User_.yearOfBirth,1970)
.startsWith(User_.lastName,"0");
List youngJoes = builder.build().find();
Lazy loading results
为了避免加载查询结果太快,query提供了findLazy()和findLazyCached方法,返回LazyList
注: LazyList: 线程安全,不可修改的List,
private DataSubscriptionList subscriptions = new DataSubscriptionList();
Query query = taskBox.query().equal(Task_.complete, false).build();
query.subscribe(subscriptions)
.on(AndroidScheduler.mainThread())
.observer(data -> updateUi(data));
subscribe()
之后调用onlyChanges()
DataObserver> taskObserver = new DataObserver>() {
@Override public void onData(Class data) {
// do something
}
};
boxStore.subscribe(Task.class).observer(taskObserver);
Query query = taskBox.query().equal(Task_.completed, false).build();
subscription = query.subscribe().observer(data -> updateUi(data));
线程调度
Query query = taskBox.query().equal(Task_.complete, false).build();
query.subscribe().on(AndroidScheduler.mainThread()).observer(data -> updateUi(data));
on()就是告知我们想要在哪个线程我们Observer被观察到
query默认在后台线程执行
数据流的改变
boxStore.subscribe()
.transform(clazz -> return boxStore.boxFor(clazz).count())
.observer(count -> updateCount(count));
我们可以在transform中对数据进行转换,比如查询出来的结果可能是List,但是你只关心数量int,就可以转换一下
但是需要引入,官方封装好的三方库,操作就和RxJava一样了
implementation "io.objectbox:objectbox-rxjava:$objectboxVersion"