专栏名称: saka
51好读  ›  专栏  ›  saka

Room使用之如何为字段设置非空约束

saka  · 掘金  ·  · 2018-05-14 03:29

正文

Room使用之如何为字段设置非空约束

发表于

Room是安卓推出的一个官方框架,极大的简化了安卓开发者中间层的编写,仅仅需要编写三个主要的注解模块即可实现增删改查功能,前一篇文章简单翻译了一下Room支持的使用,拓展了一些SQLite的知识。

其实在使用中我们会发现Room仍然有很多不尽如人意的地方,这篇文章就一个简单的非空约束设置来探索一下。

非空约束

用过SQL的人都知道用在表上的约束是一种强制规则,可以限制出入到表中的数据类型,为数据提供准确性和可靠性。SQLite中的约束主要有以下几种:

name intruduction RoomAnnotation
NOT NULL 确保列中没有NULL值 暂无
DEFAULT 没有指定时提供默认值 暂无
UNIQUEUE 列中所有的值不同 index
PRIMARY KEY 主键 PrimaryKey
CHECK 确保列中的值满足一定条件 暂无

这篇文章主要是研究如何设置NOT NULL约束。

在SQLite语句中可以直接设置非空约束:

CREATE TABLE IF NOT EXISTS `user` 
(`uid` INTEGER NOT NULL,
`user_name` TEXT NOT NULL,
`password` TEXT,
`age` INTEGER NOT NULL,
PRIMARY KEY(`uid`))

这样我们就设置了uid、username和age字段不为空。

Room原理

Room算是一个庞大的库,但我们在gradle文件中最少的情况下只需要设置两个库就可以:


implementation "android.arch.persistence.room:runtime:$project.ext.room_version"
annotationProcessor "android.arch.persistence.room:compiler:$project.ext.room_version"

打开mvn我们可以看到他们两个所依赖的库到底有多少:

runtime

compiler

关于非空约束的设置在这里要将的主要是Compiler库,它是apt解析注解生成文件的主要库。

Room中大量使用了注解来标识数据存储信息和查询信息,这些注解的元注解全部使用了 @Retention(RetentionPolicy.CLASS) ,也就是这些注解只保存到编译期,运行期就会消除(这里简单说一下其实在Room运行的过程中还是用到了反射,在获取DAO和Database实现类的时候)。它通过使用apt来获取注解信息并通过javapoet来生成实现类的代码,然后由Runtime来调用这些实现类。

这里我做一个简单的例子来说明:

首先实现一个Entity类:

@Entity(tableName = "user", indices = {@Index(name = "name", value = {"user_name"}, unique = true)})
public class User {

@PrimaryKey(autoGenerate = false)
private int uid;

@ColumnInfo(name = "user_name")
private String userName;

@ColumnInfo(name = "password")
private String password = "123456";

private Integer age;
//省略getter和setter方法
}

这个非常简单,只有四个字段,uid是int类型,设置为了主键,username是String类型,重命名为user_name,password是String类型,age是Integet类型。

然后继续实现一个Database类:

@Database(entities = {User.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();//一个简单的接口,读者可以自行实现,与本文无关
}

这里将User实体类加入到了APPDatabase数据库中了,UserDao是的一个Dao层接口,需要在这里引入为抽象域。这样我们就完成了我们自己的编码工作,
这个类在编译期间将会生成一个名称为 AppDatabase_Impl 的实现类(位置在 ./app/build/generated/source/apt/debug/debug/package/AppDatabase_Impl ),该文件完成了数据库的创建,打开连接,删除,增删改查的实现类的初始化等工作。RoomDatabase是这个实现类的父类的父类,这是一个抽象类,共有三个抽象方法:


//创建数据库,打开连接
protected abstract SupportSQLiteOpenHelper createOpenHelper(DatabaseConfiguration config);

//同步内存中的数据和数据库的数据
protected abstract InvalidationTracker createInvalidationTracker();

//清空所有数据
public abstract void clearAllTables();

同时加上AppDatabase中的UserDao抽象方法,共有四个方法需要在 AppDatabase_Impl 中实现,关于表的常见主要是第一个方法,该方法返回的示意 SupportSQLiteOpenHelper 类型,该类型是由 SupportSQLiteOpenHelper.Configuration 中的工厂方法创建,configuration本身是一个构造者模式,需要配置一个 SupportSQLiteOpenHelper.Callback ,通过代理需要实现四个主要方法:

protected abstract void dropAllTables(SupportSQLiteDatabase database);

protected abstract void createAllTables(SupportSQLiteDatabase database);

protected abstract void onOpen(SupportSQLiteDatabase database);

protected abstract void onCreate(SupportSQLiteDatabase database);

创建表的方法就在 createAllTables ,主要看一下这个方法:

@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `user` (`uid` INTEGER NOT NULL, `user_name` TEXT, `password` TEXT, `age` INTEGER, PRIMARY KEY(`uid`))");
_db.execSQL("CREATE UNIQUE INDEX `name` ON `user` (`user_name`)");
_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"1099dac99d3db917b94721c51358fa94\")");
}

只需要关注第一个执行语句,可以看到,只有uid字段被设置为了NOT NULL,其他字段都没有默认这个属性,假如多写几个变量可以很轻松的知道,所有的基本类型都会设置默认非空,除此之外都不会有这个约束,同样为非基本类型设置了PrimaryKey属性,也不会生成这个约束。

源码探索

Room本身是一个庞大的库,这里只会分析用到的一些东西,同时代码生成库COmpiler官方用的是kotlin语言,鉴于我的kotlin停留在不入门级别,有错误希望读者指正。

代码生成库用到的是compiler和common库(源码位置: asop/framewirks/support/room )

compiler库是生成代码的主要库,所有的实现类都是在这个库中由系统自动生成,找到 RoomDatabase ,这个是入口类。

override fun initSteps(): MutableIterable<ProcessingStep>? {
val context = Context(processingEnv)
return arrayListOf(DatabaseProcessingStep(context))
}

这个方法是apt的主要方法,compiler提供了一个contex(非activity的context),context是运行apt时的上下文,提供了许多有用的工具类和方法,包括日志输出,控制镇检查,注解缓存等. class DatabaseProcessingStep(context: Context) : ContextBoundProcessingStep(context) 类里边定义了生成代码的规则.

//主要方法
override fun process(elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>)
: MutableSet<Element> {






请到「今天看啥」查看全文