-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Java|Kotlin 模型绑定
模型绑定(Object-relational Mapping,简称 ORM),通过对 Java/Kotlin 类进行绑定,形成类或结构 - 表模型、类或结构对象 - 表的映射关系,从而达到通过对象直接操作数据库的目的。
WCDB Java/Kotlin 的模型绑定分为五个部分:
- 字段映射
- 字段约束
- 索引
- 表约束
- 虚拟表映射
这其中大部分是格式化的模版代码,我们在最后介绍文件模版和代码提示模版,以简化模型绑定的操作。
WCDB Java/Kotlin 的字段映射都是使用注解来配置的。以下是一个字段映射的示例代码:
//Java
@WCDBTableCoding
public class Sample {
@WCDBField(columnName = "identifier")
public int id;//只支持绑定 public 的属性
@WCDBField
public String content;
@WCDBField(columnName = "db_offset")
public int offset;
private String debugContent;
}
//Kotlin
@WCDBTableCoding
class Sample {
@WCDBField(columnName = "identifier")
var id: Int = 0//只支持绑定 public 的属性
@WCDBField
var content: String? = null
@WCDBField(columnName = "db_offset")
var offset: Int = 0
var debugContent: String? = null
}
将一个Java/Kotlin类进行ORM绑定的过程如下:
- 使用
WCDBTableCoding
注解标记Sample
类,声明它实现了模型绑定。 - 使用
WCDBField
注解配置需要绑定到数据库表的字段,这样数据库中的列名和字段名是一样的。 - 对于字段名与表的列名不一样的情况,可以使用别名进行映射,如
@WCDBField(columnName = "identifier")
。 - 对于字段名与 SQLite 的保留关键字冲突的字段,同样可以使用别名进行映射,如
offset
是 SQLite 的关键字,就需要@WCDBField(columnName = "db_offset")
。 - 对于不需要写入数据库的字段,则不需要用
WCDBField
注解标记,比如debugContent
字段。
字段映射定义完成后,先编译一下让注解生效,再调用 createTable(String, TableBinding<T>)
接口即可根据这个定义创建表。下面示例中所用到的DBSample
相关内容,都是apt或ksp根据字段映射配置生成的内容。
// 以下代码等效于 SQL:CREATE TABLE IF NOT EXISTS sampleTable(identifier INTEGER, description TEXT)
//Java
database.createTable("sampleTable", DBSample.INSTANCE);
//Kotlin
database.createTable("sampleTable", DBSample)
并非所有类型的变量都支持被绑定为字段。WCDB Java/Kotlin 内建了常用类型的支持,包括:
数据库中的类型 | 类型 |
---|---|
整型 |
boolean , byte , short , int , long 以及它们对应的封装类 |
浮点型 |
float , double 以及它们对应的封装类 |
字符串类型 | String |
二进制类型 | Java的byte[] 和Kotlin的ByteArray |
字段约束是针对单个字段的约束,如主键约束、非空约束、唯一约束、默认值等。
以下是一个字段约束的示例代码:
//Java
@WCDBTableCoding
public class Sample {
@WCDBField(isPrimary = true)
public int id;
@WCDBField(isNotNull = true)
@WCDBDefault(textValue = "defaultContent")
public String content;
}
//Kotlin
@WCDBTableCoding
class Sample {
@WCDBField(isPrimary = true)
var id: Int = 0
@WCDBField(isNotNull = true)
@WCDBDefault(textValue = "defaultContent")
var content: String? = null
}
字段约束主要是通过WCDBField
注解来配置,可配置的内容如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface WCDBField {
String columnName() default "";//字段名,不配置则使用属性名
boolean isPrimary() default false;//该字段是否为主键。字段约束中只能同时存在一个主键
boolean isAutoIncrement() default false;// 当该字段是主键时,其是否支持自增。只有整型数据可以定义为自增
boolean enableAutoIncrementForExistingTable() default false;//如果以前没有配置主键自增,已经建好表,配置这个还可以将表改为主键自增
boolean isUnique() default false;// 该字段是否可以具有唯一性
boolean isNotNull() default false;// 该字段是否可以为空
boolean isNotIndexed() default false;//fts表的配置,配置之后当前字段就不参与建索引
}
以上约束按需进行定义或者不定义即可。
字段默认值的配置则是需要额外使用WCDBDefault
来配置,可以配置整型、浮点型、字符串三种默认值:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface WCDBDefault {
long intValue() default 0;
double doubleValue() default 0;
String textValue() default "";
}
以上约束按需进行定义或者不定义即可。
定义完成后,同样调用 createTable(String, TableBinding<T>)
接口即可根据这个定义创建表。
// 以下代码等效于 SQL:CREATE TABLE IF NOT EXISTS sampleTable(id INTEGER PRIMARY KEY, content TEXT NOT NULL DEFAULT 'defaultContent')
//Java
database.createTable("sampleTable", DBSample.INSTANCE);
//Kotlin
database.createTable("sampleTable", DBSample)
同时配置了 isPrimary
和isAutoIncrement
的字段,支持以自增的方式进行插入数据。但仍可以通过非自增的方式插入数据。
当需要进行自增插入时,对象需设置主键属性为0,则数据库会使用已有数据中最大的值+1 作为主键的值,同时插入之后会把主键的插入值赋值到对应的属性。
//Java
Sample sample = new Sample();
// 插入自增数据
database.insertObject(sample, DBSample.allFields(), "sampleTable");
System.out.print(sample.id);//输出1
// 再次插入自增数据
database.insertObject(sample, DBSample.allFields(), "sampleTable");
System.out.print(sample.id);//输出2
// 插入非自增的指定数据
Sample specificSample = new Sample();
specificSample.id = 10;
database.insertObject(specificSample, DBSample.allFields(), "sampleTable");//插入的主键将会是指定的10
// 再次插入自增数据
database.insertObject(sample, DBSample.allFields(), "sampleTable");
System.out.print(sample.id);//输出11
//Kotlin
val sample = Sample()
// 插入自增数据
database.insertObject(sample, DBSample.allFields(),"sampleTable")
print(sample.id)//输出1
// 再次插入自增数据
database.insertObject(sample, DBSample.allFields(),"sampleTable")
print(sample.id)//输出2
// 插入非自增的指定数据
val specificSample = Sample()
specificSample.id = 10
database.insertObject(specificSample, DBSample.allFields(),"sampleTable")//插入的主键将会是指定的10
// 再次插入自增数据
database.insertObject(sample, DBSample.allFields(),"sampleTable")
print(sample.id)//输出12
单字段的索引可以使用WCDBIndex
注解配置,配置索引后的数据在能有更高的查询效率。多字段的索引配置请看下一节。
以下是一个定义索引的示例代码:
//Java
@WCDBTableCoding
public class Sample {
@WCDBField(isPrimary = true)
public int id;
@WCDBField
@WCDBIndex(isUnique = true)
public int subId;
@WCDBField
@WCDBIndex(name = "length_index")
public double length;
}
//Kotlin
@WCDBTableCoding
class Sample {
@WCDBField(isPrimary = true)
var id: Int = 0
@WCDBField
@WCDBIndex(isUnique = true)
var subId: Int = 0
@WCDBField
@WCDBIndex(name = "length_index")
var length: Double = 0.0
}
其中可以在WCDBIndex
中配置isUnique
来配置唯一性索引。索引名在不指定时,会用表名和字段名的组合来作为索引名,假如表名是"sampleTable",subId字段的索引名就是"sampleTable_subId_index"。
索引定义完成后,同样调用 createTable(String, TableBinding<T>)
接口即可根据这个定义创建表和相关的索引。
// 以下代码等效于 SQL:
// CREATE TABLE IF NOT EXISTS sampleTable(id INTEGER PRIMARY KEY, subId INTEGER, length REAL)
// CREATE UNIQUE INDEX IF NOT EXISTS sampleTable_subId_index on sampleTable(subId)
// CREATE INDEX IF NOT EXISTS length_index on sampleTable(length)
//Java
database.createTable("sampleTable", DBSample.INSTANCE);
//Kotlin
database.createTable("sampleTable", DBSample)
表约束的配置都是在WCDBTableCoding
中,它的可配置内容如下:
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface WCDBTableCoding {
MultiIndexes[] multiIndexes() default {}; //多字段索引
MultiPrimary[] multiPrimaries() default {};//联合主键
MultiUnique[] multiUnique() default {}; //多字段唯一约束
boolean isWithoutRowId() default false; //是否有rowid
FTSModule ftsModule() default @FTSModule();//配置FTS虚表
}
以下是一个表约束的示例代码:
//Java
@WCDBTableCoding(
multiPrimaries = @MultiPrimary(columns = {"multiPrimaryKeyPart1", "multiPrimaryKeyPart2"}),
multiUnique = @MultiUnique(columns = {"multiUniquePart1", "multiUniquePart1"}),
multiIndexes = @MultiIndexes(columns = {"multiIndexPart1", "multiIndexPart2"})
)
public class Sample {
@WCDBField(isPrimary = true) public int id;
@WCDBField public int multiPrimaryKeyPart1;
@WCDBField public int multiPrimaryKeyPart2;
@WCDBField public int multiUniquePart1;
@WCDBField public int multiUniquePart2;
@WCDBField public int multiIndexPart1;
@WCDBField public int multiIndexPart2;
}
//Kotlin
@WCDBTableCoding(
multiPrimaries = [MultiPrimary(columns = ["multiPrimaryKeyPart1", "multiPrimaryKeyPart2"])],
multiUnique = [MultiUnique(columns = ["multiUniquePart1", "multiUniquePart1"])],
multiIndexes = [MultiIndexes(columns = ["multiIndexPart1", "multiIndexPart2"])]
)
class Sample {
@WCDBField(isPrimary = true) var id = 0
@WCDBField var multiPrimaryKeyPart1 = 0
@WCDBField var multiPrimaryKeyPart2 = 0
@WCDBField var multiUniquePart1 = 0
@WCDBField var multiUniquePart2 = 0
@WCDBField var multiIndexPart1 = 0
@WCDBField var multiIndexPart2 = 0
}
注意,这里表约束中的
columns
中用的是DB字段的名字。如果属性名和字段名不一致时,要区分使用DB字段名。
约束的定义方式与索引类似。定义完成后,同样调用 createTable(String, TableBinding<T>)
接口即可根据这个定义创建表。
// 以下代码等效于 SQL:
// CREATE TABLE IF NOT EXISTS sampleTable(
// identifier INTEGER PRIMARY KEY,
// multiPrimaryKeyPart1 INTEGER,
// multiPrimaryKeyPart2 INTEGER,
// multiUniquePart1 INTEGER,
// multiUniquePart1 INTEGER,
// CONSTRAINT PRIMARY KEY(multiPrimaryKeyPart1, multiPrimaryKeyPart2),
// CONSTRAINT UNIQUE(multiUniquePart1, multiUniquePart2)
// )
// CREATE INDEX IF NOT EXISTS sampleTable_multiIndexPart1_multiIndexPart2_index on sampleTable(multiIndexPart1, multiIndexPart2)
//Java
database.createTable("sampleTable", DBSample.INSTANCE);
//Kotlin
database.createTable("sampleTable", DBSample)
虚拟表映射主要是通过WCDBTableCoding
中的ftsModule
来配置,它用于定于虚拟表以进行全文搜索等特性。
普通表不需要用到虚拟表映射,因此这里暂且按下不表,我们会在全文搜索一章中进行介绍。
在开发过程中,经过多个版本的迭代后,经常会出现数据库字段升级的情况,如增加新字段、删除或重命名旧字段、新增索引等等。 对于 SQLite 本身,其并不支持对字段的删除和重命名。新增加字段则需要考虑不同版本升级等情况。而这个问题通过模型绑定可以很好的解决。
纵观上述字段映射、字段约束、索引和表约束等四个部分,都是通过调用 createTable(String, TableBinding<T>)
接口使其生效的。
实际上,该接口会将 模型绑定的定义 与 表本身的结构 联系起来,并进行更新。
对于字段映射:
- 表已存在但模型绑定中未定义的字段,会被忽略。这可以用于删除字段。
- 表不存在但模型绑定中有定义的字段,会被新增到表中。这可以用于新增字段。
- 对于需要重命名的字段,可以通过别名的方式重新映射。
忽略字段并不会删除字段。对于该字段旧内容,会持续存在在表中,因此文件不会因此变小。实际上,数据库作为持续增长的二进制文件,只有将其数据导出生成另一个新的数据库,才有可能回收这个字段占用的空间。对于新插入的数据,该字段内容为空,不会对性能产生可见的影响。
对于索引,不存在的索引会被新增到数据库中。
对于数据库已存在但模型绑定中未定义的索引,
createTable(String, TableBinding<T>)
接口不会自动将其删除。如果需要删除,开发者需要调用dropIndex(String)
接口。
以下是数据库升级的一个例子:
在第一个版本中,Sample
的模型绑定定义如下,并在数据库创建了以之对应的表 sampleTable。
//Java
@WCDBTableCoding
public class Sample {
@WCDBField public int id;
@WCDBField public String content;
@WCDBField public int createDate;
}
database.createTable("sampleTable", DBSample.INSTANCE);
//Kotlin
@WCDBTableCoding
class Sample {
@WCDBField var id = 0
@WCDBField var content: String? = null
@WCDBField var createDate: Int = 0
}
database.createTable("sampleTable", DBSample)
到了第二个版本,sampleTable 表进行了升级。
//Java
@WCDBTableCoding
public class Sample {
@WCDBField public int id;
@WCDBField(columnName = "content") public String description;
@WCDBField @WCDBIndex public String title;
}
database.createTable("sampleTable", DBSample.INSTANCE);
//Kotlin
@WCDBTableCoding
class Sample {
@WCDBField var id = 0
@WCDBField(columnName = "content") var description: String? = null
@WCDBField @WCDBIndex var title: String? = null
}
database.createTable("sampleTable", DBSample)
可以看到,通过修改模型绑定,并再次调用 createTable(String, TableBinding<T>)
-
content
字段通过别名的特性,被重命名为了description
。 - 已删除的
createDate
字段会被忽略。 - 对于新增的
title
会被添加到表中。 - 新增的索引
sampleTable_title_index
会被添加到表中。
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程