Skip to content

Java|Kotlin 数据库、表、事务

qiuwenchen edited this page Mar 7, 2024 · 1 revision

本文主要介绍 WCDB Java/Kotlin 的三个基础类:数据库 - Database、表 - Table 和 事务。

数据库

Database 是 WCDB Java/Kotlin 中最基础的类,几乎所有操作都由该类发起。

初始化

Database 可以通过文件路径来创建,而且 WCDB 会自动创建中间路径上的文件夹:

//Java
Database database = new Database("~/Intermediate/Directories/Will/Be/Created/sample.db");
//Kotlin
val database = Database("~/Intermediate/Directories/Will/Be/Created/sample.db")

标签

通过设置标签,可以区分不同的数据库。

//Java
int myTag1 = 1;
Database database1 = new Database(path1);
database1.setTag(1);

int myTag2 = 2;
Database database2 = new Database(path2);
database2.setTag(2);
//Kotlin
val myTag1 = 1
val database1 = Database(path1)
database1.tag = 1

val myTag2 = 2
val database2 = Database(path2)
database2.tag = 2

同时,基于基础类数据共享的机制,对于同一路径的基础类,它们会共享同一个 tag

//Java
System.out.print(database1.getTag());//输出1

Database anotherDatabase = new Database(path1);
System.out.print(anotherDatabase.getTag());//输出1
//Kotlin
print(database1.tag)//输出1

val anotherDatabase = Database(path1)
print(anotherDatabase.tag)//输出1

打开数据库

延迟初始化是 WCDB Java/Kotlin 的原则之一,绝大部分数据只会在需要用到时才创建并初始化。数据库的打开就是其中一个例子。

数据库会在第一次进行操作时,自动打开并初始化。开发者不需要手动调用

//Java
Database database = new Database(filePath);
System.out.print(database.isOpened());// 输出 false
database.createTable("sampleTable", DBSample.INSTANCE);
System.out.print(database.isOpened());// 输出 true
//Kotlin
val database = Database(filePath)
print(database.isOpened) // 输出 false
database.createTable("sampleTable", DBSample)
print(database.isOpened) // 输出 true

同时,也可以通过 canOpen 接口测试数据库能否正常打开。

//Java
Database database1 = new Database(filePath);
System.out.print(database1.isOpened());// 输出 false
System.out.print(database1.canOpen());// 输出 true。仅当数据库无法打开时,如路径无法创建等,该接口会返回 false
System.out.print(database1.isOpened());// 输出 true
Database database2 = new Database(filePath);
System.out.print(database2.isOpened());// 输出 true。WCDB 同一路径的数据库共享数据和状态等。
//Kotlin
val database1 = Database(filePath)
print(database1.isOpened) // 输出 false
print(database1.canOpen()) // 输出 true。仅当数据库无法打开时,如路径无法创建等,该接口会返回 false
print(database1.isOpened) // 输出 true
val database2 = Database(filePath)
print(database2.isOpened) // 输出 true。WCDB 同一路径的数据库共享数据和状态等。

关闭数据库

与打开数据库相对应,关闭数据库一般情况下也不需要开发者手动调用。当同个路径下的Database对象全部释放时,数据库会自动关闭,并回收内存。

当然,也可以调用 close 接口,手动关闭数据库。

//Java
Database database = new Database(filePath);
System.out.print(database.canOpen());// 输出 true
System.out.print(database.isOpened());// 输出 true
database.close();
System.out.print(database.isOpened());// 输出 false
//Kotlin
val database = Database(filePath)
print(database.canOpen()) // 输出 true
print(database.isOpened) // 输出 true
database.close()
print(database.isOpened) // 输出 false

WCDB Java/Kotlin 也提供了 blockadeunblockadeisBlockaded 接口用于分步执行关闭数据库操作,可参考相关接口文档

关闭数据库与线程安全

某些情况下,开发者需要确保数据库完全关闭后才能进行操作,如将数据库整体zip打包发送。

数据库是二进制文件,zip打包的过程中若数据发生了变化,则打包后的文件数据可能会不完整、损坏。因此,WCDB Java/Kotlin 提供了 close(CloseCallBack) 接口。

//Java
database.close(new Database.CloseCallBack() {
    @Override
    public void onClose() throws WCDBException {
        //执行独占的数据库操作,如zip压缩打包
    }
});
//Kotlin
database.close {
    //执行独占的数据库操作,如zip压缩打包
}

onClosed 回调内,可确保数据库完全关闭,不会有其他线程的数据访问、操作数据库,因此可以安全地操作文件。

内存回收

purge 接口用于回收暂不使用的内存。

// 回收 database 数据库中暂不使用的内存
database.purge() 
// 回收所有已创建的数据库中暂不使用的内存
Database.purge() 

文件操作

//Java
// 获取所有与该数据库相关的文件路径
System.out.print(database.getPaths());
// 获取所有与该数据库相关的文件占用的大小
database.close(new Database.CloseCallBack() {
    @Override
    public void onClose() throws WCDBException {
        // 数据库未关闭状态下也可获取文件大小,但不够准确,开发者可自行选择是否关闭
        System.out.print(database.getFileSize());
    }
});
// 删除所有与该数据库相关的文件
database.removeFiles();
// 将所有与该数据库相关的文件移动到另一个目录
database.moveFile(otherDirectory);
//Kotlin
// 获取所有与该数据库相关的文件路径
print(database.paths)
// 获取所有与该数据库相关的文件占用的大小
database.close { // 数据库未关闭状态下也可获取文件大小,但不够准确,开发者可自行选择是否关闭
    print(database.fileSize)
}
// 删除所有与该数据库相关的文件
database.removeFiles()
// 将所有与该数据库相关的文件移动到另一个目录
database.moveFile(otherDirectory)

Table 指代数据库中的一个表。可以通过 getTable(String, TableBinding<T>) 接口获取。

//Java
Table<Sample> table = database.getTable("sampleTable", DBSample.INSTANCE);
//Kotlin
val table = database.getTable("sampleTable", DBSample)

表相当于指定了表名和模型绑定类的 Database,其实质只是后者的简化版。增删查改中提到的所有接口Table都具备,而且这些接口调用时都不需要再传表名和 ORM 类型,下面是使用示例:

//Java
//需要指定表名和Field数组,返回值需指定为 Sample 类型以匹配范型
List<Sample> objectsFromDatabase = database.getAllObjects(DBSample.allFields(), "sampleTable");

//table 已经指定了表名和模型绑定的类,因此可以直接获取
List<Sample> objectsFromTable = table.getAllObjects();
// Kotlin        
//需要指定表名和Field数组,返回值需指定为 Sample 类型以匹配范型
val objectsFromDatabase = database.getAllObjects(DBSample.allFields(), "sampleTable")

//table 已经指定了表名和模型绑定的类,因此可以直接获取
val objectsFromTable = table.allObjects

因为执行数据读写时Table使用起来比Database更加简洁,而且也有利于以表为单位来管理数据读写逻辑,所以我们推荐尽量使用Table来进行数据读写,而且table的生命管理可以和database同步。

事务

事务一般用于 提升性能保证数据原子性DatabaseTable 都能直接发起事务,也可以通过 Transaction 更好地控制事务。

//Java
Table<Sample> table = database.getTable("sampleTable", DBSample.INSTANCE);
database.runTransaction(new Transaction() {
    @Override
    public boolean insideTransaction(Handle handle) throws WCDBException {
        table.insertObject(sample);
        table.deleteObjects(DBSample.id.eq(1));
        return true;//返回true提交事务,返回false则回滚事务
    }
});
//Kotlin
val table = database.getTable("sampleTable", DBSample)
database.runTransaction {
    table.insertObject(sample)
    table.deleteObjects(DBSample.id.eq(1))
    true //返回true提交事务,返回false则回滚事务
}

性能

事务提升性能的实质是批量处理。

//Java
Sample sample = new Sample();
List<Sample> samples = Collections.nCopies(100000, sample);

// 单独插入,效率很差
for(Sample obj : samples){
    table.insertObject(obj);
}

// 事务插入,性能较好
database.runTransaction(new Transaction() {
    @Override
    public boolean insideTransaction(Handle handle) throws WCDBException {
        for(Sample obj : samples){
            table.insertObject(obj);
        }
        return true;
    }
});

// insert 接口内置了事务,并对批量数据做了针对性的优化,性能更好
table.insertObjects(samples);
//Kotlin
val sample = Sample()
val samples = Collections.nCopies(100000, sample)

// 单独插入,效率很差
for (obj in samples) {
    table.insertObject(obj)
}

// 事务插入,性能较好
database.runTransaction {
    for (obj in samples) {
        table.insertObject(obj)
    }
    true
}

// insert 接口内置了事务,并对批量数据做了针对性的优化,性能更好
table.insertObjects(samples)

原子性

试考虑以下代码:

//Java
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        table.deleteObjects();
    }
});
thread.start();

table.insertObject(sample);
Value count = table.getValue(Column.all().count());
System.out.print(count);// 可能输出 0 或 1
//Kotlin
val thread = Thread { 
    table.deleteObjects() 
}
thread.start()

table.insertObject(sample)
val count = table.getValue(Column.all().count())
print(count) // 可能输出 0 或 1

Column.all().count()表示SQL语法中的count(*),这个在语言集成查询中会有进一步介绍。

在多线程下,删除操作发生的时机是不确定的。倘若它发生在 插入完成之后取出数据之前 的瞬间,则 getAllObjects() 无法取出刚才插入的数据,且这种多线程低概率的 bug 是很难查的。

而事务可以保证一段操作的原子性:

//Java
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        table.deleteObjects();
    }
});
thread.start();

database.runTransaction(new Transaction() {
    @Override
    public boolean insideTransaction(Handle handle) throws WCDBException {
        table.insertObject(sample);
        Value count = table.getValue(Column.all().count());
        System.out.print(count);// 输出 1
        return true;
    }
});
//Kotlin
val thread = Thread { 
    table.deleteObjects() 
}
thread.start()

database.runTransaction {
    table.insertObject(sample)
    val count = table.getValue(Column.all().count())
    print(count) // 输出 1
    true
}

执行事务

WCDB 提供了三种事务,普通事务、嵌入事务和可中断事务。

//Java
// 普通事务
database.runTransaction(new Transaction() {
    @Override
    public boolean insideTransaction(Handle handle) throws WCDBException {
        table.insertObject(sample);
        return true;
    }
});

// 普通事务支持嵌套调用
database.runTransaction(new Transaction() {
    @Override
    public boolean insideTransaction(Handle handle) throws WCDBException {
        database.runTransaction(new Transaction() {
            @Override
            public boolean insideTransaction(Handle handle) throws WCDBException {
                table.insertObject(sample);
                return true;
            }
        });
        return true;
    }
});
//Kotlin
// 普通事务
database.runTransaction {
    table.insertObject(sample)
    true
}

// 普通事务支持嵌套调用
database.runTransaction {
    database.runTransaction {
        table.insertObject(sample)
        true
    }
    true
}

普通事务可以通过返回值控制提交或者回滚事务,返回true提交事务,返回false则回滚事务。

普通事务可以互相嵌套执行,嵌套时支持只回滚嵌套事务中的局部更改内容。

insertObjectsinsertOrReplaceObjectsinsertOrIgnoreObjectscreateTable 等 WCDB 自带的接口都使用了嵌入事务

WCDB 也提供了 beginTransactioncommitTransactionrollbackTransaction 接口用于分步执行事务,可参考相关接口文档

关于可中断事务,因为使用较为复杂,后面在高级接口中会有介绍。

Clone this wiki locally