🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 1. 前言 当然,我们所使用的就是之前使用过的`SQLite`数据库。可以简单回顾一下在`java`中是如何操作数据库的: - 继承自`SQLiteOpenHelper`类,复写对应的方法,可以得到一个`Helper`实例; - 通过`SQLiteOpenHelper`的实例的`getWritableDatabase()`来得到一个数据库实例; - 然后就可以通过这个数据库实例进行`CRUD`操作; 简单回顾一下在`Java`中的流程: ``` // 构建一个子类Helper public class MySQLiteOpenHelper extends SQLiteOpenHelper { private Context context; private String name; private String bookSql = "create table Book (id integer primary key autoincrement, " + "name text, pages integer)"; private String userSql = "create table User (name text, age integer)"; public MySQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); this.context = context; this.name = name; } @Override public void onCreate(SQLiteDatabase db) { // 创建数据库表 db.execSQL(bookSql); db.execSQL(userSql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists Book"); db.execSQL("drop table if exists User"); onCreate(db); // 重新执行一下onCreate方法 } } // 获取db对象 mySQLiteOpenHelper = new MySQLiteOpenHelper(getApplicationContext(), "BookDatabase.db", null, 3); SQLiteDatabase db= mySQLiteOpenHelper.getWritableDatabase(); // CRUD db.insert("Book", null, values); ``` # 2. Kotlin中的数据库操作 虽然在`Kotlin`中也可以像上面的那种方式一样来进行数据库的操作,但是`Google`推出了一款数据库框架,即:`Room`。下面就使用这个框架进行完成操作。 ## 2.1 依赖 首先需要添加依赖: ~~~ // Room def room_version = "2.2.6" implementation "androidx.room:room-runtime:$room_version" // For Kotlin use kapt instead of annotationProcessor (注意这个注释) kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" testImplementation "androidx.room:room-testing:$room_version" ~~~ 当然,这里在`kotlin`中使用`kapt`,我们需要导入这个插件: ~~~ plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-kapt' } ~~~ 注释:如果项目是使用`Kotlin`语言来开发的,在添加`room-compiler`的时候使用`kapt`关键字,`java`语言开发的就使用`annotationProcessor`关键字。 ## 2.2 基础概念 要想使用`Room`,必须要了解最基础的三个概念: * `Entity`:实体类,对应的是数据库的一张表结构。需要使用注解 `@Entity` 标记。默认实体类的类名为表名,字段名为数据库中的字段。 * `Dao`:包含访问一系列访问数据库的方法。需要使用注解 `@Dao` 标记。 * `Database`:数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。需要使用注解 `@Database` 标记。 使用`@Database`注解需满足以下条件: * 定义的类必须是一个继承于`RoomDatabase`的抽象类。 * 在注解中需要定义与数据库相关联的实体类列表。 * 包含一个没有参数的抽象方法并且返回一个带有注解的 `@Dao`。 注释:以上基础概念摘自:[Jetpack架构组件 — Room入坑详解](https://blog.csdn.net/singwhatiwanna/article/details/104890202/) ### 2.2 1 @Entity 从前面我们知道,`@Entity`作用在类上,该类对应数据库中的一个数据表。属性对应数据库中的字段,那么类似的我们可以指定主键和注释。同样也是使用注解: * `@PrimaryKey`注解用来标注表的主键,可以使用`autoGenerate = true `来指定了主键自增长; * `@ColumnInfo`注解用来标注表对应的列的信息比如表名、默认值等等。 * `@Ignore` 注解顾名思义就是忽略这个字段,使用了这个注解的字段将不会在数据库中生成对应的列信息。 ## 2.2.2 @Dao `Dao`类是一个接口,其中定义了一系列的操作数据库的方法。`Room`也为我们的提供了相关的注解,有`@Insert`、`@Delete`、`@Update` 和 `@Query`。 比如: ``` @Query("select * from user where userId = :id") fun getUserById(id: Long): User ``` ### 2.2.3 @Database 首先需要定义一个类,继承自`RoomDatabase`,并添加注解 `@Database` 来标识。 ## 2.3 实战 这里我们需要一个数据库来存储用户记事本的数据,大致包括如下内容: | 字段 | 说明 | | --- | --- | | `title` | 标题 | | `content` | 数据内容 | | `firstSubmit` | 首次提交时间 | | `lastModifiy` | 最后一次修改时间 | | `type` | 类型,普通记事或者待办 | | `label` | 标签,支持多个,使用`#`号分割 | | `category` | 分类,工作/学习/生活/... | | `groupId` | 组号,默认为1,表示单个记录;如果为多个,表示前端显示为重叠 | 那么首先我们需要使用`@Entity`注解来生成逻辑的表(`MFNote`): ~~~ @Entity class MFNote { @PrimaryKey(autoGenerate = true) var noteId: Int = 0 @ColumnInfo(defaultValue = "无标题") lateinit var title: String @ColumnInfo(defaultValue = "") lateinit var content: String @ColumnInfo(name = "first_submit") var submit: String? = null @ColumnInfo(name = "last_modify") var modify: String? = null var type: Int = 0 @ColumnInfo(defaultValue = "默认") lateinit var label: String @ColumnInfo(defaultValue = "默认") lateinit var category: String @ColumnInfo(name = "group_id") var groupId: Int = 1 } ~~~ 然后构建一个访问`MFNote`表的`DAO`接口(`MFDao`): ~~~ @Dao interface MFDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(mfNote: MFNote?) @Delete fun delete(mfNote: MFNote): Int @Query("select * from MFNote") fun getAllNotes(): List<MFNote> @Query("select * from MFNote where type = :type") fun getNotesByType(type: Int): MFNote @Update fun updateNoteByNote(mfNote: MFNote) } ~~~ 参数`onConflict`,表示的是当插入的数据已经存在时候的处理逻辑,有三种操作逻辑:`REPLACE`、`ABORT`和`IGNORE`。如果不指定则默认为`ABORT`终止插入数据。这里我们将其指定为`REPLACE`替换原有数据。 最后需要构建`Room`使用的入口`RoomDatabase`。 ~~~ @Database(entities = [MFNote::class], version = 1) abstract class MFNoteDataBase : RoomDatabase() { abstract fun mfDao(): MFDao companion object { @Volatile private var mInstance: MFNoteDataBase? = null private const val DATABASE_NAME = "MFNote.db" @JvmStatic fun getInstance(context: Context): MFNoteDataBase? { if (mInstance == null) { synchronized(MFNoteDataBase::class.java) { if (mInstance == null) { mInstance = createInstance(context) } } } return mInstance } private fun createInstance(context: Context): MFNoteDataBase { mInstance = Room.databaseBuilder( context.applicationContext, MFNoteDataBase::class.java, DATABASE_NAME ).build() return mInstance as MFNoteDataBase } } } ~~~ 在这里我们只需要对一个数据库表进行操作,所以就定义了一个抽象接口。如果需要定义多个,比如下面的写法: ~~~ @Database(entities = [User::class, Course::class, Teacher::class, UserJoinCourse::class, IDCard::class], version = 1) abstract class AppDataBase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun teacherDao(): TeacherDao abstract fun courseDao(): CourseDao abstract fun userJoinCourseDao(): UserJoinCourseDao abstract fun idCardDao(): IDCardDao } ~~~ * `@Database` 表示继承自`RoomDatabase`的抽象类,`entities`指定表的实现类列表,`version`指定了`DB`版本 * 必须提供获取`DAO`接口的抽象方法,比如上面定义的`movieDao()`,`Room`将通过这个方法实例化`DAO`接口 接下来就是调用了: ~~~ class TestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) // 调用Room数据库 val mfDao = MFNoteDataBase.getInstance(this)?.mfDao() mfDao?.insert(MFNote()) mfDao?.getAllNotes()?.forEach { Log.e("TAG", "onCreate: ${it.title}, ${it.category}" ) } } } ~~~ 结果: ![](https://img.kancloud.cn/c1/b3/c1b336e0a90bdcafefcb865e72ad5e2b_1833x271.png) 最终我在`Dao`层添加了如下方法: ~~~ @Dao interface MFDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(mfNote: MFNote?) @Delete fun delete(mfNote: MFNote): Int @Query("select * from MFNote") fun getAllNotes(): List<MFNote> @Query("select * from MFNote where noteId = :id") fun getNoteByNoteId(id: Int): MFNote @Query("select * from MFNote where type = :type") fun getNotesByType(type: Int): List<MFNote> @Query("select * from MFNote where label like '%' || :label || '%'") fun getNotesByLabel(label: String): List<MFNote> @Query("select * from MFNote where category like '%' || :category || '%'") fun getNotesByCategory(category: String): List<MFNote> @Query("select * from MFNote where group_id = :groupId") fun getNotesByGroupId(groupId: Int): List<MFNote> @Query("select * from MFNote where first_submit >= :beginTime and first_submit <= :endTime") fun getNotesBySubmitTime(beginTime: String, endTime: String): List<MFNote> @Query("select * from MFNote where first_submit >= :beginTime") fun getNotesByStartSubmitTime(beginTime: String): List<MFNote> @Query("select * from MFNote where first_submit <= :endTime") fun getNotesByEndSubmitTime(endTime: String): List<MFNote> @Query("select * from MFNote where last_modify >= :beginTime and last_modify <= :endTime") fun getNotesByModifyTime(beginTime: String, endTime: String): List<MFNote> @Query("select * from MFNote where last_modify >= :beginTime") fun getNotesByStartModifyTime(beginTime: String): List<MFNote> @Query("select * from MFNote where last_modify <= :endTime") fun getNotesByEndModifyTime(endTime: String): List<MFNote> @Query("select * from MFNote where (title like '%' || :words || '%') or (content like '%' || :words || '%') or (first_submit like '%' || :words || '%') or (last_modify like '%' || :words || '%') or (label like '%' || :words || '%') or (category like '%' || :words || '%')") fun getNodesByKeyWords(words: String): List<MFNote> @Update fun updateNoteByNote(mfNote: MFNote) } ~~~ 测试代码: ~~~ class TestActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_test) // 调用Room数据库 val mfDao = MFNoteDataBase.getInstance(this)?.mfDao() val mfNote = MFNote() mfNote.title = "测试" mfNote.content = "第一条测试" mfDao?.insert(mfNote) mfDao?.getAllNotes()?.forEach { Log.e("TAG", "onCreate: ${it.title}, ${it.category}" ) } val notesByType = mfDao?.getNotesByType(0) notesByType?.forEach { Log.e("TAG", "onCreate: ${it.title}, ${it.category}" ) } val entity = mfDao?.getNoteByNoteId(2) Log.e("TAG", "onCreate: ${entity?.title}, ${entity?.firstSubmit}" ) entity?.title = "厉害" mfDao?.updateNoteByNote(entity!!) } } ~~~ 以上代码均测试通过。