墙裂推荐36e(36eb)
微信公众号:北风中独行的蜗牛一.
微信公众号:北风中独行的蜗牛一. 简介与导入Andorid官方中推荐Room代替SQlite,所以新的项目中直接舍弃了以前用的第三那方框架greenDaoRoom由三部分组成,并且用三个注解标注:Entity: 这个注解表示的是实体类,代表的是数据库中的表,每一个实体类都是一张表
Dao:改注解标注的是一个接口,接口中封装的是操作数据库的方法,比如增删改查database:这个注解标注的是一个数据的持有者,他是一个抽象类,并且持有一个接口dao的抽象方法,还需在这个抽象类中添加上所有实体的标识,另外他需要继承RoomDatabase。
其实原理是在编译阶段,根据注解,就会编译出具体的实现类,所以对于我们来说这就简单的很多,因为所有的代码都是编译器根据规则生成的所以我们需要再gradle中加入:apply plugin: kotlin-android
apply plugin: kotlin-kapt需要依赖:implementation "androidx.room:room-runtime:2.3.0"kapt "androidx.room:room-compiler
:2.3.0"implementation "androidx.room:room-ktx:2.3.0"加入kapt的目的就是编译注解kapt是一个注解处理插件二 . 应用1. 利用注解entity定义实体类。
比如:@Entity(tableName = "student")dataclassStudent( val name: String, val age:String, ) {
@PrimaryKey(autoGenerate = true)var id:Long = 0L } Entity注解标记这个类是一个表,可以自定义表明tableName,如果不指定的话默认是类的名字,一个表中必须指定一个主键,如果不指定主键编译会报错,可以使用注解@PrimaryKey 指定某一个属性为主键,其中autoGenerate表示的是自增,如果不在属性上标记主键的话,可以再@Entity上面指定,比如指定姓名为主键:
@Entity(tableName = "student", primaryKeys = ["name"])也可以指定联合主键:@Entity(tableName = "student", primaryKeys = [
"name", "id"])Entity这个注解还有其他的几个值,但是不怎么常用Index[] indices() default {}; 索引,可以为表添加索引 boolean inheritSuperIndices
() defaultfalse; 表示的是父类的索引是否可以被当前的类继承 ForeignKey[] foreignKeys() default {}; 需要依赖的外键,现在基本上没人会用外键 ,都是根据逻辑关系控制具体的数据
String[] ignoredColumns() default {}; 可以忽略的字段 当数据量很大的时候,可以为了快速查找,可以为某一列或者多列添加索引,不过一般应该用不到,主要是Android端一般也不会存储特别大的数据量
@Entity(tableName = "student", indices = [Index("name"), Index(value = ["name", "age"])]) 还有一个就是外键,用处不大,一般没人用,了解下就好:
@Entity(tableName = "student", foreignKeys = [ForeignKey(entity = ClassRoom::class,parentColumns= [
"id"], childColumns = ["classRoomId"])]) parentColumns 指的是所依赖的表的主键,childColumns 当前表中的所依赖的表的id
默认情况下,Entity标注的类中的属性值都是表中的一列,如果某一列不需要存储在表中,可以用ignoredColumns 进行忽略,不过一般都是用单独的注解进行忽略,比如:@Ignore val classRoomId:
String 如果列不想用默认的属性名字的话可以用@ColumnInfo进行指定:@ColumnInfo(name = "age")val age:String, ColumnInfo这个注解下面有几个属性可以使用:
String defaultValue()default VALUE_UNSPECIFIED; 设置默认值 booleanindex()defaultfalse; 是不是索引列 @Collateintcollate
()default UNSPECIFIED; 列的排列顺序 设置默认值可能会用,其他的两个基本上也不会用2. 定义Dao,用于操作数据,进行增删改查定义的dao是一个接口,用Dao进行标注,例如查找所有的学生:
@DaointerfaceStudentDao{ @Query("select * from student")funfindAll():List } 如果根据条件查询的话,那可可以进行将值传过来,写where语句例如:
@Query("select * from student where id = :id ")funfindById(id:Long): Student :后面的参数即为方法中的参数,需要多少传多少即可
其实这样一个方法的上面只要写sql就好了,还是很简单的,再比如,相求 总的数据条数:@Query("select count(*) from student")funfindCount():Int是不是很简单,当然如果你想联合查询某几个表,只是保留某几个字段的话,那就需要指定列名,并且和你返回的实体类的属性一一对应即可,比如:
@Query("select s.name as studentName, r.class_name as roomName from student as s left join class_room as r on r.class_id = s.roomId"
)fungetAllName():List dataclassStudentRoom(val studentName:String,val roomName:String) {}
insert也很简单,直接用@Insert注解即可@InsertfuninsertStudent(student: Student)如果想确定是不是插入成功的的话,可以加一个返回参数的,返回的是你插入的id吧
当然可以批量插入,用可变参数或者list都可以,返回的结果也是一个list,表示每一个插入成功的id其实编译之后,可以点进去看看源码的:@OverridepubliclonginsertStudent(
final Student student){ __db.assertNotSuspendingTransaction(); __db.beginTransaction(); try {
long _result = __insertionAdapterOfStudent.insertAndReturnId(student); __db.setTransactionSuccessful();
return _result; } finally { __db.endTransaction(); } } 明显返回的是ID也可以用可变参数,kotlin是vararg表示可变参数更新和删除的话,可以这么干:
@UpdatefunupdateStudent(student: Student)@DeletefundeleteStudent(student: Student)都是根据主键进行更新和删除的,但是这里有一个问题是,更新的话就对于这个主键的一行数据全部更新了,所以有时候只需要更新某一个字段的话,就需要用另外一种方法了,使用sql语句
比如 我只想更新学生的名字:@Query("update student set name = :name where id = :id")funupdateNameForStudent(name:String
,id:Long)同理,delete其实也可以写sql语句3. 定义database有了表和操作方法,没有数据库,所以我们需要定义一个数据库,数据库中指定该数据库中有哪些表和操作方法,需要一个抽象方法,并继承RoomDatabase,如下:
@Database(entities = [Student::class, ClassRoom::class], version = 1)abstractclassAppDatabase : RoomDatabase
(){ abstractfunstudentDao(): StudentDao companionobject { @Volatileprivatevar INSTANCE: AppDatabase?=
nullfungetInstance(context: Context): AppDatabase = INSTANCE?: synchronized(this){ INSTANCE?:buildDatabase(context).also { INSTANCE = it } }
privatefunbuildDatabase(context: Context) = Room.databaseBuilder(context.applicationContext, AppDatabase::
class.java, "hello_wold.db") .allowMainThreadQueries() .build() } }
entities 指定所有的实体类,version指定当前数据库的版本,并且里面有一个抽象方法,返回值就是你的dao,这就可以了,剩下的编译插件会自动给你实现接下来就是创建数据库了,创建书数据库的方法是这个:。
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "hello_wold.db")
.allowMainThreadQueries() .build() 指定你的数据库抽象类和数据库的名称,并创建,这个地方法你可以再任何用到数据库的地方执行,但是通常我们会写在database中,并改造成单例模式,这是避免有多个数据的引用,避免资源的浪费,到这儿基本就结束了,剩下的就是调用了, .allowMainThreadQueries() 这个方法是运行在主线程中调用,如果不加的话,在主线程调用会报错的,比如插入一个学生:
AppDatabase.getInstance(this).studentDao() .insertStudent(Student("Marry","24",1)) 4. 数据库的升级与降级表的增加,表的修改,是避免不了的,所以这个时候需要表的升级
比如我增加了一个地址表的话就应该,加上address这个实体类,并将版本号升级@Database(entities = [Student::class, ClassRoom::class, Address:
:class], version = 2) 如果只是这样,那app就挂了,还需要加上一些其他的处理,有时候升级的时候,可能需要删除某些数据,并将数据迁移等等,还需要一个方法:Room.databaseBuilder(context.applicationContext, AppDatabase::
class.java, "hello_wold.db") .addMigrations(MIGRATION_1_2) .allowMainThreadQueries() .build()
privateval MIGRATION_1_2 = object : Migration(1,2){ overridefunmigrate(database: SupportSQLiteDatabase
) { } } 每升级一次,都需要加一个这个方法,比如2到3privatefunbuildDatabase(context: Context) = Room.databaseBuilder(context.applicationContext, AppDatabase::
class.java, "hello_wold.db") .addMigrations(MIGRATION_1_2, MIGRATION_2_3) .allowMainThreadQueries() .build()
privateval MIGRATION_1_2 = object : Migration(1,2){ overridefunmigrate(database: SupportSQLiteDatabase
) { } } privateval MIGRATION_2_3 = object : Migration(2,3){
overridefunmigrate(database: SupportSQLiteDatabase) { } } 升级的规则是每升一级都要有一个相应的方法,如果直接第一个版本升级到第三个版本的话,默认会先执行升级到2,然后升级到3
另外还有一个特别需要注意的问题是,如果添加了新表的话,不只需要在database这个类中entities 指定,还需要再升级的时候去写sql创建表,才行,否则会报错比如我想加一个address的表,@Database(entities = [Student::class, ClassRoom::class, Address::class], version = 2)
privateval MIGRATION_1_2 = object : Migration(1,2){ overridefunmigrate(database: SupportSQLiteDatabase
) { database.execSQL("CREATE TABLE IF NOT EXISTS `address` (`addressId` INTEGER NOT NULL, `addressName` TEXT NOT NULL, PRIMARY KEY(`addressId`))"
) } } 这样才行,另外不要自己写这个sql语句,去你自动生成的创建语句copy,否则,自己写如果和自动生成的create语句不一致的话,也会报错默认生成的create的语句在你的database_impl这个类中。
publicvoidcreateAllTables(SupportSQLiteDatabase_db){_db.execSQL("CREATETABLEIFNOTEXISTS`student`(`id`
INTEGERPRIMARYKEYAUTOINCREMENTNOTNULL,`name`TEXTNOTNULL,`age`TEXTNOTNULL,`roomId`INTEGERNOTNULL)");_db.execSQL("CREATE
TABLEIFNOTEXISTS`class_room`(`class_id`INTEGERNOTNULL,`class_name`TEXTNOTNULL,PRIMARYKEY(`class_id`))");
_db.execSQL("CREATETABLEIFNOTEXISTS`address`(`addressId`INTEGERNOTNULL,`addressName`TEXTNOTNULL,PRIMARY
KEY(`addressId`))");_db.execSQL("CREATETABLEIFNOTEXISTSroom_master_table(idINTEGERPRIMARYKEY,identity_hash
TEXT)");_db.execSQL("INSERTORREPLACEINTOroom_master_table(id,identity_hash)VALUES(42,7cbdd6263025181ec070edd36e1118eb
)");另外升级添加字段的时候,一定不要忘了 添加默认值,否则也会有问题的如果数据库要降级的话需要添加这.fallbackToDestructiveMigration(),默认删除所有的表,重新创建,当然所有的数据也就没了
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "hello_wold.db") .addMigrations
(MIGRATION_1_2, MIGRATION_2_3) .fallbackToDestructiveMigration() .allowMainThreadQueries()
.build() 5. 表关联Room也是关系型数据库,所以表和表之间可以有一对一的关系,一对多的关系,多对多的关系一对一的关系比如一个学生对应一个教室:根据这个学生查出所对应的教室:学生表:@Entity
(tableName = "student") data class Student( val name: String, val age:String, val roomId:Long ) {
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id:Long = 0L } 教室表:@Entity(tableName =
"class_room") data class ClassRoom(@PrimaryKey val class_id:Long, var class_name:String) {} 查询所对应的关系表:
dataclassStudentRoom( @Embedded val student: Student, @Relation( parentColumn = "roomId"
, entityColumn = "class_id" ) val classRoom: ClassRoom ) {} 对应的查询语句:@Query("select * from student"
)fungetAllStudent():List 多对一的的话,那就是一个教室对应多个学生,相对来说也很简单:dataclassStudentRoom( @Embedded
val student: Student, @Relation( parentColumn = "roomId", entityColumn = "class_id"
) val classRoom: ClassRoom ) {} 查询语句:@Query("select * from class_room")fungetAllRoom():List
还有一种多对多的关系,暂时先不看了,也用不到三. 其他可能会用的一点技巧TypeConverter有时候数据库中的一些数据是无法存储的,比如Student,每一个学生都有些朋友,朋友很多,我只想存储下他的名字,那就是一个list:
dataclassStudent( val name: String, val age:String, val roomId:Long, val friend:List ) {
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id:Long = 0L } 这个时候肯定会报错,因为数据库不明白你这个list是什么东西,所以我们可以对他进行转换,比如说转成string类型,所以就有了一个注解TypeConverter:
classMyConverters{ @TypeConverterfunlistToString(value:List):String{ val sb = StringBuilder() value.forEach { sb.append(
",").append(it) } return sb.toString().substring(1) } @TypeConverterfunstringToList
(value:String):List{ return value.split(",") } } 定义一个类,类中有两个方法,一个是转string,一个是转list,这两个一定要成对出现,用于实现自动转换,接着在实体类上标注下即可:
@Entity(tableName = "student") @TypeConverters(MyConverters::class) data class Student( val name: String, val
age:String, val roomId:Long, val friend:List ) { @PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id:Long = 0L } 系统会自动转list和string,比如insert语句中,生成的代码:_tmp = __myConverters.listToString(
value.getFriend()); publicvoidbind(SupportSQLiteStatement stmt, Student value) { stmt.bindLong(1
, value.getId()); if (value.getName() == null) { stmt.bindNull(2); } else { stmt.bindString(
2, value.getName()); } if (value.getAge() == null) { stmt.bindNull(3); } else { stmt.bindString(
3, value.getAge()); } stmt.bindLong(4, value.getRoomId()); final String _tmp; _tmp = __myConverters.listToString(
value.getFriend()); if (_tmp == null) { stmt.bindNull(5); } else { stmt.bindString(
5, _tmp); } } }; Embedded还有一个注解 可能也会用到 @Embedded,这个表示嵌套对象比如Student中我使用@Embedded 嵌套一个实体类,这个实体类就是一个普通的类
@Entity(tableName = "student") data class Student( val name: String, val age:String, val
roomId:Long, @Embedded val test: Test ) { @PrimaryKey(autoGenerate = true) @ColumnInfo(name =
"id") var id:Long = 0L } data class Test(val testName:String) { } test中表的字段会在创建student表的时候创建出来,所以student会多出testName这一列。
rxjava2还可以使用rxjava,进行异步操作,需要引入依赖implementation "androidx.room:room-rxjava2:2.3.0"比如:@Query("select * from student"
)fungetAllStudent():Observable
免责声明:本站所有信息均搜集自互联网,并不代表本站观点,本站不对其真实合法性负责。如有信息侵犯了您的权益,请告知,本站将立刻处理。联系QQ:1640731186