# 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}" )
}
}
}
~~~
結果:

最終我在`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!!)
}
}
~~~
以上代碼均測試通過。
- Kotlin語言基礎
- Kotlin的簡介
- Kotlin的變量和常見數據類型
- Kotlin的區間
- Kotlin的位運算
- Kotlin的容器
- Kotlin類型檢查
- Kotlin的空值處理
- Kotlin的函數
- Kotlin的類
- Kotlin的委托
- Kotlin的延遲加載
- Kotlin的異常
- Kotlin的Lambda表達式
- Kotlin的高階函數
- Kotlin的標準庫中的高階函數
- Kotlin的泛型
- Kotlin的表達式
- Kotlin的解構
- Kotlin的運算符重載
- Kotlin語言中級
- Kotlin的擴展函數
- Kotlin的擴展屬性
- Kotlin的infix關鍵字
- Kotlin的DSL
- Kotlin的一些注解(和Java互調用)
- Kotlin的lateinit和by lazy
- Kotlin的反射
- Kotlin的匿名接口
- 安卓中的Kotlin
- 數據庫操作Room