正文
Room使用之如何为字段设置非空约束
发表于
2018-05-14
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我们可以看到他们两个所依赖的库到底有多少:
关于非空约束的设置在这里要将的主要是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> {