简单介绍
最开始学习安卓的时候,会继承自SQLiteOpenHelper来编写自己的数据库;之后一些优秀的三方开源数据库框架出现了,我用过的有郭神的LitePal、greenrobot的greenDao(EventBus也是他编写的);为了帮助支持各位开发者,google推出了自己的数据库框架Room。
官方定义
官方介绍:The Room persistence library provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite.
翻译过来就是,Room persistence库在SQLite上提供了一个抽象层,允许流畅的数据库访问,同时利用SQLite的全部功能。
样例效果
写的demo很简单,大家凑合看:
使用方式
首先我们需要在项目的build.gradlle文件添加google的maven仓库(as高版本中自动添加的就有)
allprojects { repositories { jcenter() google() } }
接着在主module的build.gradle文件添加依赖
implementation 'android.arch.persistence.room:runtime:1.1.1' annotationProcessor 'android.arch.persistence.room:compiler:1.1.1' implementation 'android.arch.persistence.room:testing:1.1.1'
接下来建立一个实体类吧,Room中的表无需像原生一样sql语句来创建,类似于greendao,都i是通过实体类映射的方式来创建。里面有几个注解,感觉和greendao差不多。
1、 @Entity(tableName="表名称")
此注解放在实体类的类名上面,可以设置表的名称、主键、外键等信息,源码如下:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface Entity { String tableName() default ""; Index[] indices() default {}; boolean inheritSuperIndices() default false; String[] primaryKeys() default {}; ForeignKey[] foreignKeys() default {}; }
2、@PrimaryKey(autoGenerate = true)
此注解放在主键变量上,并定义自增
3、@ColumnInfo(name = "字段名称")
此注解放在普通变量上,字段映射具体的数据表字段名
4、@Ignore
这个注解需要好好说一下,可放在方法和变量上。Room定义必须有一个无参的构造方法,所以说如果你想有别的构造方法的话需要在别的构造方法上添加上此注解。如果说你实体类中的字段不是表中的字段,这时也需要忽略下,需要在变量上添加此注解。
好了,介绍完这些让我们来看一下实体类最终样子(get和set方法必须要生成):
/** * 设备类(外部数据库) * * @author jiang zhu on 2019/10/25 */ //entity声明定义,并且指定了映射数据表明 @Entity(tableName = "equip_type") public class EquipType { //设置主键,并且定义自增增 @PrimaryKey(autoGenerate = true) private int id; //字段映射具体的数据表字段名 @ColumnInfo(name = "name") private String name; //字段映射具体的数据表字段名 @ColumnInfo(name = "component") private int component; //字段映射具体的数据表字段名 @ColumnInfo(name = "code") private String code; //字段映射具体的数据表字段名 @ColumnInfo(name = "default_line_type") private int defaultLineType; //字段映射具体的数据表字段名 @ColumnInfo(name = "icon") private String icon; //字段映射具体的数据表字段名 @ColumnInfo(name = "remark") private String remark; //必须指定一个构造方法,room框架需要。并且只能指定一个 //,如果有其他构造方法,则其他的构造方法必须添加@Ignore注解 public EquipType() { } //其他构造方法要添加@Ignore注解 @Ignore public EquipType(int id, String name, int component, String code, int defaultLineType, String icon, String remark) { this.id = id; this.name = name; this.component = component; this.code = code; this.defaultLineType = defaultLineType; this.icon = icon; this.remark = remark; } @Override public String toString() { return "EquipType{" + "id=" + id + ", name='" + name + '\'' + ", component=" + component + ", code='" + code + '\'' + ", defaultLineType=" + defaultLineType + ", icon='" + icon + '\'' + ", remark='" + remark + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getComponent() { return component; } public void setComponent(int component) { this.component = component; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public int getDefaultLineType() { return defaultLineType; } public void setDefaultLineType(int defaultLineType) { this.defaultLineType = defaultLineType; } public String getIcon() { return icon; } public void setIcon(String icon) { this.icon = icon; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } }
增删改查
数据库最重要的就是增删改查,这个Room和greendao不太一样:room需要自己来写dao(数据操作 增删改查)。那就一个一个来吧:
1.增加 @Insert(onConflict = OnConflictStrategy.REPLACE)
OnConflictStrategy.REPLACE表如已有数据,就覆盖掉。数据的判断通过主键进行匹配,也就是id,非整个EquipType对象。增加分为两种,一种是增加一条、另外一种是增加多条数据,
//返回Long数据表示,插入条目的主键值(id) @Insert(onConflict = OnConflictStrategy.REPLACE) Long insert(EquipType equipType); //返回List<Long>数据表示被插入数据的主键id列表 @Insert(onConflict = OnConflictStrategy.REPLACE) List<Long> insertAll(List<EquipType> equipTypeList);
2.删除 @Delete
删除就比较简单了,直接添加注解返回int处理成功条数即可。
@Delete int delete(EquipType equipType); @Delete int deleteAll(List<EquipType> equipTypeList);
3.修改 @Update
修改也一样,需要传入修改完的实体类,其中必须要有主键(通过主键来进行修改)
@Update() int update(EquipType equipType); @Update() int updateAll(EquipType... equipTypes);
4.查询 @Query("SELECT * FROM equip_type")
查询灵活性就很大了,可以直接里面写sql语句。很赞的是:Room可以直接判断你写的表名是否存在,还和参数值一一对应。
@Query("SELECT * FROM equip_type") List<EquipType> getAll(); //参数cid和注解中的cid相对应 注解中的equip_type和id和实体类中的相对应 @Query("SELECT * FROM equip_type WHERE id = :cid") LiveData<EquipType> findByUid(int cid);
数据库创建
表的实体类在上面已经写完,接下来创建一下数据库。需要继承自RoomDatabase。实现如下:
/** * @author jiang zhu on 2019/11/12 */ //注解指定了database的表映射实体数据以及版本等信息 @Database(entities = {EquipType.class}, version = 1) public abstract class ZhuDatabase extends RoomDatabase { //RoomDatabase提供直接访问底层数据库实现,我们通过定义抽象方法返回具体Dao //然后进行数据库增删该查的实现。 public abstract EquipTypeDao equipTypeDao(); }
需要在创建的类的上面添加@Database注解,其中的entities是一个数组,可以添加多个类,相应的在下面可以创建多个抽象方法(多个表的情况下),以便使用。
接下来需要创建数据库了:
//得到ZhuDatabase 对象 private var db : ZhuDatabase? = null //得到Dao对象 private var equipTypeDao : EquipTypeDao? = null private fun initData() { db = Room.databaseBuilder( applicationContext, ZhuDatabase::class.java, "room-database" ) //添加数据库变动迁移 //.addMigrations(AppDatabase.MIGRATION_1_2) //默认设置为不可在主线程执行,需要重新开启线程 // .allowMainThreadQueries() .build() equipTypeDao = db!!.equipTypeDao() }
可在里面设置数据库的迁移(下面会说到)和是否可在主线程操作,不建议在主线程操作,建议放在子线程(数据库操作比较耗时)。
实现功能
首先创建布局,代码太简单就不在这里放代码了,布局样式如下,
然后创建按钮的点击事件:
override fun onClick(v: View) { when (v.id) { R.id.mainBtnAddOne -> { insertOne() } R.id.mainBtnAddSome -> { insertSome() } R.id.mainBtnDeleteOne -> { deleteOne() } R.id.mainBtnDeleteSome -> { deleteSome() } R.id.mainBtnUpdateOne -> { updateOne() } R.id.mainBtnUpdateSome -> { updateSome() } R.id.mainBtnQueryOne -> { queryOne() } R.id.mainBtnQuerySome -> { querySome() } } }
由于代码比较多,在这里只展示比较重要的代码。
下面是增加一条数据的代码,这里用到了线程池,需要的可以访问我的github,地址会在文章末尾贴出。
//增加一条 private fun insertOne() = threadPool.execute { val result = equipTypeDao!!.insert( EquipType( 1, "朱江", 2, "你猜", 3, "http://192.168.8.18:81/zentao/file-read-4910.png", "呵呵" ) ) runOnUiThread { if (result > 0) ToastUtils.showShort("新增成功") } }
增加多条数据代码类似,只是插入时放了一个集合。下面是查询一条数据的代码,
//查询一条 private fun queryOne() = threadPool.execute{ val equipType = equipTypeDao!!.findByUid(1) runOnUiThread { equipType.observe(this, Observer { mainTvResult.text =it.toString() }) } }
数据库升级
这里已经完成了数据库的基本操作,上面也提到了数据库的变动迁移,在ZhuDatabase中定义方法进行操作即可,下面是代码示例:
//数据库变动添加Migration public static final Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLiteDatabase database) { //数据库的具体变动 //类型是integer,不为空,默认值是0 database.execSQL("ALTER TABLE equip_type " + " ADD COLUMN number INTEGER NOT NULL DEFAULT 0"); } };