ObjectBox学习笔记

仲高超
2023-12-01

ObjectBox简介

ObjectBox数据库是greenrobot团队开发的全新非关系型数据库框架,具有如下特点:

1、超快速:号称胜过测试过的所有嵌入式数据库;

2、面向对象的API:没有rows、columns和SQL,完全从0开始的对象;

3、反应性:对数据变化的反应简单而强大,使用ObjectBox中数据观察器或与RxJava集成;

4、多平台支持:已经支持安卓和java、ios和MacOs正在集成;

5、简单的线程:ObjectBox返回的对象可以在所有线程中运行;

6、不需要手动模式迁移:ObjectBox负责添加,删除和重命名属性的新对象版本,意味着数据库的升级不再需要我们自己管理版本号,ObjectBox内部会自动帮我们管理;

7、ObjectBox小于1MB,因此它是移动应用程序、小型IoT设备和IoT网关的理想解决方案。

ObjectBox环境配置

1、project根目录下build.gradle (project 级别)添加如下配置:

buildscript {
    ...
    ext.objectboxVersion = '2.8.1'
    dependencies {
        ...
        classpath "io.objectbox:objectbox-gradle-plugin:$objectboxVersion"
    }
}

2、浏览ObjectBox数据

2.1如果需要通过浏览器(PC端或移动端)查看ObjectBox存储的数据,需执行下面操作:

a、在module根目录下build.gradle (module 级别)添加如下配置:

dependencies {
    ...

    // 使用浏览器查看数据的依赖,官方建议仅在 debug 版本依赖,所以做以下区分
    debugImplementation "io.objectbox:objectbox-android-objectbrowser:$objectboxVersion"
    releaseImplementation "io.objectbox:objectbox-android:$objectboxVersion"
}

// 注意下面的内容必须放到 dependencies 语句块的后面(文件末尾),否则编译报错
apply plugin: 'io.objectbox'

b、在Application的onCreate()中添加如下代码:

if (BuildConfig.DEBUG) {// 官方建议仅在debug下开启数据浏览
    new AndroidObjectBrowser(boxStore).start(app);// 注:boxStore的获取需要先创建实体类
}

c、在AndroidManifest.xml中添加相应权限:

<!-- Required to provide the web interface -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- Required to run keep-alive service when targeting API 28 or higher -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

d、手机需要连接adb,浏览器访问http://localhost:8090/index.html (该地址由ObjectBrowser 日志得到)。如果访问不到,那么程序安装后,打开命令行输入以下命令adb forward tcp:8090 tcp:8090后再次尝试。

I/ObjectBrowser: ObjectBrowser started: http://127.0.0.1:8090/index.html
I/ObjectBrowser: Command to forward ObjectBrowser to connected host: adb forward tcp:8090 tcp:8090

2.2如果不需要通过浏览器查看ObjectBox存储的数据,需要在module根目录下build.gradle (module 级别)添加如下配置:

apply plugin: 'com.android.application'

// after applying Android plugin
apply plugin: 'io.objectbox'

3、解决Caused by: java.lang.ClassNotFoundException: kotlin.text.Charsets 编译报错

a、尝试clean project后make project。反复多次后如果仍报错,则尝试步骤b;

b、尝试在module根目录下build.gradle (module 级别)中添加下面配置:

dependencies {
    ...
    // Optional -- manually add native ObjectBox library to override auto-detection
    testImplementation "io.objectbox:objectbox-linux:$objectboxVersion"
}

ObjectBox使用

1、创建实体类

a、相关注解

@Entity:这个对象需要持久化。

@Id:这个对象的主键,必须是long类型,默认情况下,id是会被objectbox管理的,也就是自增id,如果你想手动管理id需要在注解的时候加上@Id(assignable = true)即可。当你在自己管理id的时候如果超过long的最大值,objectbox 会报错,且id的值不能为负数。当id等于0时objectbox会认为这是一个新的实体对象,因此会新增到数据库表中。

@Index:这个对象中的索引。对经常大量进行查询的字段创建索引,会提高你的查询性能。目前@Index不能用来标识类型为 byte[]、float、double的字段。在2.0版本之前,ObjectBox 只能用@Index标识的字段的值做索引,在2.0版本之后则可以指定为不同类型来优化索引的效率。@Index(type = xxx)目前支持的类型有:默认(String 类型将默认指定为 HASH,其他类型将被指定为 VALUE)、IndexType.VALUE(使用字段的值做索引)、IndexType.HASH(使用32位的hash来构建索引,小概率会出现冲突但不影响效率)、IndexType.HASH64(使用64位的hash构建,自然会比32位占用更多空间)。

@Transient:如果你有某个字段不想被持久化,可以使用此注解,那么该字段将不会保存到数据库

@NameInDb:有的时候数据库中的字段跟你的对象字段不匹配的时候,可以使用此注解。

@ToOne:做一对一的关联注解,例如下面示例中表示一张User表关联一张Addr表。

@ToMany:做一对多的关联注解,如示例中表示一张Addr表关联多张User表。

@Backlink:表示反向关联。

@Relation:做一对多,多对一的注解。

@Unique:被标识的字段必须唯一,2.0+支持。在存入过程中,如果被@Unique标识的字段重复则会抛出UniqueViolationException异常。

b、构造函数

ObjectBox 的实体类必需一个空参数的构造函数,否则会在运行时报错: Entity is expected to have a no-arg constructor。另外,官网文档中提到,提供一个包括了全部属性的构造函数将会提升性能。

c、关于属性

ObjectBox在构建时需要访问实体类的属性,所以其属性要求有如下两个选择:

(1) 要求属性至少标识为package private(包访问权限),而不能使private。

(2) 如果要标识为private,则要求提供标准的getter和setter方法(这个标准就是使用IDE自动生成的)。

d、实例说明

@Entity
public class User {
    @Id
    @NameInDb(“_id”)
    private long id;

    @NameInDb(“name”)
    private String name;

    @NameInDb(“gender”)
    private String gender;

    @NameInDb(“age”)
    private int age;

    // ? 为什么不能指定在数据库中的别名
    public ToOne<Addr> address;
}
@Entity
public class Addr {
    @Id
    @NameInDb(Constants.Addr.ID)
    private long id;

    @NameInDb(Constants.Addr.COUNTRY)
    private String country;

    @NameInDb(Constants.Addr.CITY)
    private String city;

    @NameInDb(Constants.Addr.STREET)
    private String street;

    @Backlink(to = "address")
    public ToMany<User> users;
}

2、初始化(建议在Application中)

private BoxStore boxStore; //数据库表的管理者
private final String BD_NAME = "user";

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

public BoxStore getBoxStore(){
return boxStore;
}

public static ObjectBoxApp getApplication() {
    return this;
}

3、新增数据

BoxStore boxStore = ObjectBoxApp.getApplication().getBoxStore();
Box<User> usersBox = boxStore.boxFor(User.class);

// 新增单条记录
User user = new User().setName("张三").setAge(25).setGender("男");
Addr addr = new Addr().setCountry("中国").setCity("深圳").setStreet("南山");
user.address.setTarget(addr);
usersBox.put(user);

// 批量新增
for(User user: allUsers) {
   modify(user); // modifies properties of given user
}
box.put(allUsers);

4、删除数据

BoxStore boxStore = ObjectBoxApp.getApplication().getBoxStore();
Box<User> usersBox = boxStore.boxFor(User.class);

usersBox.query().equal(User_.name, "李四").build().remove();
usersBox.removeAll();

5、查找数据

BoxStore boxStore = ObjectBoxApp.getApplication().getBoxStore();
Box<User> usersBox = boxStore.boxFor(User.class);

User user = usersBox.query().equal(User_.name, "张三").build().findFirst();
List<User> users = usersBox.query().build().find();

6、更新数据

BoxStore boxStore = ObjectBoxApp.getApplication().getBoxStore();
Box<User> usersBox = boxStore.boxFor(User.class);

User user = usersBox.query().equal(User_.name, "张三").build().findFirst();
if (user != null) {
    user.setName("李四");
    usersBox.put(user);
}

7、表关系

@ToOne和@ToMany可以看作JAVA中的T和List的关系。如上面例子中User和Addr:

public ToOne<Addr> address;

@Backlink(to = "address")
public ToMany<User> users;

一对一关系

Addr addr = new Addr().setCountry("中国").setCity("深圳").setStreet("南山");
user.address.setTarget(addr);
addr = user.address.getTarget();

一对多关系

User user1 = new User();
User user2 = new User();
Addr addr = new Addr();
addr.users.add(user1);
addr.users.add(user2);
boxStore.boxFor(Addr.class).put(addr);

8、数据库升级

(https://docs.objectbox.io/advanced/data-model-updates)

当我们需要新增、删除属性或者重命名类名、属性名时,直接操作实体类即可,默认情况下旧的数据会丢失,为了保留旧数据则可使用@Uid注解。

修改类名

a、给需要修改的类名加上@Uid注解

@Entity
@Uid
public class User {
    ...
}

b、编译项目,编译将失败,并且会给你一个Uid

错误: [ObjectBox] UID operations for entity "User": [Rename] apply the current UID using @Uid(8056120966789605000L) - [Change/reset] apply a new UID using @Uid(2661598794239301979L)
2 个错误

c、将[Rename]后面的UID放入到要修改类的@Uid后面

@Entity
@Uid(8056120966789605000L)
public class User {
    ...
}

d、修改实体类类名

@Entity
@Uid(8056120966789605000L)
public class NewUser {
    ...
}

修改属性类型

a、在要修改的属性上加上@Uid注解

@Entity
public class User {
    ...

    @Uid
    @NameInDb(Constants.User.GENDER)
    private String gender;
}

b、编译项目,编译将失败,并且会给你一个Uid

错误: [ObjectBox] UID operations for property "User.gender": [Rename] apply the current UID using @Uid(2215757294699215868L) - [Change/reset] apply a new UID using @Uid(2071166115147421803L)
2 个错误

c、将[Change/reset]部分中的UID应用到该属性@Uid后面,然后修改其类型

@Entity
public class User {
    ...

    @Uid(2071166115147421803L)
    @NameInDb(Constants.User.GENDER)
    private int gender;
}

9、事务

Box实例下的put和remove的执行实际上已经是事务的。除此之外显性的使用事务也是可以的,ObjectBox 提供了几个api:

API

说明

runInTx

在给定的 runnable 中运行的事务

runInReadTx

只读事务,不同于 runInTx,允许并发读取

runInTxAsync

运行在一个单独的线程中执行,执行完成后,返回 callback

callInTx

与runInTx 相似,不同的是可以有返回值

参考文献

参考博客:https://www.pianshen.com/article/9581153796/

参考博客:https://www.jianshu.com/p/5b348c9a7315

官网文档:https://docs.objectbox.io/getting-started

官网文档:https://docs.objectbox.io/advanced/data-model-updates

 

 

 类似资料: