一、Room
1、Room 概述
- Room 是 Jetpack 中的一个重要成员,它是一个持久化库,它为管理数据库提供了简单强大的方法
2、Room 引入
- 在模块级
build.gradle
中引入相关依赖
implementation "androidx.room:room-runtime:2.2.5"
annotationProcessor "androidx.room:room-compiler:2.2.5"
二、Room 简单案例
1、Application
- MyApplication.java
package com.my.room.application;
import android.app.Application;
import android.content.Context;
public class MyApplication extends Application {
public static final String TAG = MyApplication.class.getSimpleName();
private static Context context;
@Override
public void onCreate() {
super.onCreate();
context = this;
}
public static Context getContext() {
return context;
}
}
2、Entity
- Staff.java
package com.my.room.entity;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "Staff")
public class Staff {
@PrimaryKey(autoGenerate = true)
public Long id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "age")
public Integer age;
@ColumnInfo(name = "cardNum")
public String cardNum;
@Override
public String toString() {
return "Staff{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", cardNum='" + cardNum + '\'' +
'}';
}
}
3、DAO
- StaffDAO.java
package com.my.room.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import com.my.room.entity.Staff;
import java.util.List;
@Dao
public interface StaffDAO {
@Insert
void insert(Staff staff);
@Delete
void delete(Staff staff);
@Update
void update(Staff staff);
@Query("SELECT * FROM Staff")
List<Staff> queryAll();
}
4、Database
- MyDatabase.java
package com.my.room.database;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import com.my.room.application.MyApplication;
import com.my.room.dao.StaffDAO;
import com.my.room.entity.Staff;
@Database(entities = {Staff.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
public static final String TAG = MyDatabase.class.getSimpleName();
public static final String DATABASE_NAME = "test.db";
private static MyDatabase myDatabase;
public static MyDatabase getInstance() {
if (myDatabase == null) {
myDatabase = Room.databaseBuilder(MyApplication.getContext(), MyDatabase.class, DATABASE_NAME)
.build();
}
return myDatabase;
}
public abstract StaffDAO getStaffDAO();
}
5、Repository Observer
- InsertObserver.java
package com.my.room.repository.observer;
public interface InsertObserver {
void onSuccess();
void onError(String msg);
}
- DeleteObserver.java
package com.my.room.repository.observer;
public interface DeleteObserver {
void onSuccess();
void onError(String msg);
}
- UpdateObserver.java
package com.my.room.repository.observer;
public interface UpdateObserver {
void onSuccess();
void onError(String msg);
}
- QueryAllObserver.java
package com.my.room.repository.observer;
import java.util.List;
public interface QueryAllObserver<T> {
void onResult(List<T> ts);
}
6、Repository
- RepositoryItem.java
package com.my.room.repository;
public class RepositoryItem<T> {
public static final int CODE_SUCCESS = 1;
public static final int CODE_ERROR = 2;
private int code;
private String msg;
private T data;
public RepositoryItem(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "RepositoryItem{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
- StaffRepository.java
package com.my.room.repository;
import android.os.AsyncTask;
import com.my.room.dao.StaffDAO;
import com.my.room.database.MyDatabase;
import com.my.room.entity.Staff;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.QueryAllObserver;
import com.my.room.repository.observer.UpdateObserver;
import java.util.List;
public class StaffRepository {
private MyDatabase myDatabase;
private StaffDAO staffDAO;
public StaffRepository() {
myDatabase = MyDatabase.getInstance();
staffDAO = myDatabase.getStaffDAO();
}
// ====================================================================================================
public void insert(Staff staff, InsertObserver insertObserver) {
new InsertAsyncTask(staff, insertObserver).execute();
}
public void delete(Staff staff, DeleteObserver deleteObserver) {
new DeleteAsyncTask(staff, deleteObserver).execute();
}
public void update(Staff staff, UpdateObserver updateObserver) {
new UpdateAsyncTask(staff, updateObserver).execute();
}
public void queryAll(QueryAllObserver<Staff> queryAllObserver) {
new QueryAllAsyncTask(queryAllObserver).execute();
}
// ====================================================================================================
private class InsertAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {
private Staff staff;
private InsertObserver insertObserver;
public InsertAsyncTask(Staff staff, InsertObserver insertObserver) {
this.staff = staff;
this.insertObserver = insertObserver;
}
@Override
protected RepositoryItem doInBackground(Void... voids) {
try {
staffDAO.insert(staff);
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem repositoryItem) {
super.onPostExecute(repositoryItem);
if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
insertObserver.onSuccess();
} else {
insertObserver.onError(repositoryItem.getMsg());
}
}
}
private class DeleteAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {
private Staff staff;
private DeleteObserver deleteObserver;
public DeleteAsyncTask(Staff staff, DeleteObserver deleteObserver) {
this.staff = staff;
this.deleteObserver = deleteObserver;
}
@Override
protected RepositoryItem<Object> doInBackground(Void... voids) {
try {
staffDAO.delete(staff);
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
super.onPostExecute(repositoryItem);
if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
deleteObserver.onSuccess();
} else {
deleteObserver.onError(repositoryItem.getMsg());
}
}
}
private class UpdateAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {
private Staff staff;
private UpdateObserver updateObserver;
public UpdateAsyncTask(Staff staff, UpdateObserver updateObserver) {
this.staff = staff;
this.updateObserver = updateObserver;
}
@Override
protected RepositoryItem<Object> doInBackground(Void... voids) {
try {
staffDAO.update(staff);
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
super.onPostExecute(repositoryItem);
if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
updateObserver.onSuccess();
} else {
updateObserver.onError(repositoryItem.getMsg());
}
}
}
private class QueryAllAsyncTask extends AsyncTask<Void, Void, RepositoryItem<List<Staff>>> {
private QueryAllObserver<Staff> queryAllObserver;
public QueryAllAsyncTask(QueryAllObserver<Staff> queryAllObserver) {
this.queryAllObserver = queryAllObserver;
}
@Override
protected RepositoryItem<List<Staff>> doInBackground(Void... voids) {
try {
List<Staff> staffs = staffDAO.queryAll();
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, staffs);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem<List<Staff>> repositoryItem) {
super.onPostExecute(repositoryItem);
queryAllObserver.onResult(repositoryItem.getData());
}
}
}
7、Activity Layout
- activity_staff.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".StaffActivity">
<Button
android:id="@+id/btn_insert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="增"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="删"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_insert" />
<Button
android:id="@+id/btn_update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="改"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_delete" />
<Button
android:id="@+id/btn_query_all"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_update" />
</androidx.constraintlayout.widget.ConstraintLayout>
8、Activity Code
- StaffActivity.java
package com.my.room;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import com.my.room.entity.Staff;
import com.my.room.repository.StaffRepository;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.UpdateObserver;
public class StaffActivity extends AppCompatActivity {
public static final String TAG = StaffActivity.class.getSimpleName();
private StaffRepository staffRepository;
private Button btnInsert;
private Button btnDelete;
private Button btnUpdate;
private Button btnQueryAll;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_staff);
staffRepository = new StaffRepository();
btnInsert = findViewById(R.id.btn_insert);
btnDelete = findViewById(R.id.btn_delete);
btnUpdate = findViewById(R.id.btn_update);
btnQueryAll = findViewById(R.id.btn_query_all);
btnInsert.setOnClickListener(v -> {
Staff staff = new Staff();
staff.name = "jack";
staff.age = 20;
staff.cardNum = "staff-001";
staffRepository.insert(staff, new InsertObserver() {
@Override
public void onSuccess() {
Log.i(TAG, "------------------------------ insert success");
}
@Override
public void onError(String msg) {
Log.i(TAG, "------------------------------ insert error - " + msg);
}
});
});
btnDelete.setOnClickListener(v -> {
Staff staff = new Staff();
staff.id = 1L;
staffRepository.delete(staff, new DeleteObserver() {
@Override
public void onSuccess() {
Log.i(TAG, "------------------------------ delete success");
}
@Override
public void onError(String msg) {
Log.i(TAG, "------------------------------ delete error - " + msg);
}
});
});
btnUpdate.setOnClickListener(v -> {
Staff staff = new Staff();
staff.id = 1L;
staff.name = "jack";
staff.age = 30;
staff.cardNum = "staff-002";
staffRepository.update(staff, new UpdateObserver() {
@Override
public void onSuccess() {
Log.i(TAG, "------------------------------ update success");
}
@Override
public void onError(String msg) {
Log.i(TAG, "------------------------------ update error - " + msg);
}
});
});
btnQueryAll.setOnClickListener(v -> {
staffRepository.queryAll(staffs -> {
if (staffs == null) {
Log.i(TAG, "------------------------------ queryAll - staffs is null");
return;
}
if (staffs.size() == 0) {
Log.i(TAG, "------------------------------ queryAll - staffs is empty");
return;
}
for (Staff staff : staffs)
Log.i(TAG, "------------------------------ queryAll - " + staff);
});
});
}
}
Test
- 增 -> 查 -> 改 -> 查 -> 删 -> 查,输出结果
I/StaffActivity: ------------------------------ insert success
I/StaffActivity: ------------------------------ queryAll - Staff{id=1, name='jack', age=20, cardNum='staff-001'}
I/StaffActivity: ------------------------------ update success
I/StaffActivity: ------------------------------ queryAll - Staff{id=1, name='jack', age=30, cardNum='staff-002'}
I/StaffActivity: ------------------------------ delete success
I/StaffActivity: ------------------------------ queryAll - staffs is empty
三、Room 简单案例解析
1、Entity 解析
@Entity(tableName = "Staff")
public class Staff {
@PrimaryKey(autoGenerate = true)
public Long id;
@ColumnInfo(name = "name")
public String name;
@ColumnInfo(name = "age")
public Integer age;
@ColumnInfo(name = "cardNum")
public String cardNum;
@Override
public String toString() {
return "Staff{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", cardNum='" + cardNum + '\'' +
'}';
}
}
@Entity(tableName = "Staff")
:指定 Staff 类为 Room 数据库中 Staff 表对应的实体类
-
@PrimaryKey(autoGenerate = true)
:指定 id 字段为主键,并且它的值是自动生成的 -
@ColumnInfo(name = "name")
:指定 name 字段映射到表中的 name 列,@ColumnInfo(name = "age")
和@ColumnInfo(name = "cardNum")
同理
- Staff 表会在 APP 首次安装时由 Room 根据 Staff 类创建,SQL 语句如下
CREATE TABLE IF NOT EXISTS Staff (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
age INTEGER,
cardNum TEXT
);
2、DAO 解析
@Dao
public interface StaffDAO {
@Insert
void insert(Staff staff);
@Delete
void delete(Staff staff);
@Update
void update(Staff staff);
@Query("SELECT * FROM Staff")
List<Staff> queryAll();
}
@Dao
:指定 StaffDAO 接口与 Room 数据库进行交互,以操作 Staff 类,该接口使用 Room 相关注解来定义操作方法
-
@Insert
:插入数据,这里传递一个 Staff 对象作为参数,Room 将这个对象插入到数据库中 -
@Delete
:删除数据,这里传递一个 Staff 对象作为参数,Room 基于该对象的 id 字段来删除相应的行 -
@Update
:修改数据,这里传递一个 Staff 对象作为参数,Room 基于该对象的 id 字段来修改相应的行 -
@Query("SELECT * FROM Staff")
:查询数据,查询表中的所有行
3、Database 解析
@Database(entities = {Staff.class}, version = 1, exportSchema = false)
public abstract class MyDatabase extends RoomDatabase {
public static final String TAG = MyDatabase.class.getSimpleName();
public static final String DATABASE_NAME = "test.db";
private static MyDatabase myDatabase;
public static MyDatabase getInstance() {
if (myDatabase == null) {
myDatabase = Room.databaseBuilder(MyApplication.getContext(), MyDatabase.class, DATABASE_NAME)
.build();
}
return myDatabase;
}
public abstract StaffDAO getStaffDAO();
}
(1)注解解析
@Database
:指定 MyDatabase 抽象类为 Room 数据库
-
entities = {Staff.class}
:指定这个数据库包含 Staff 类 -
version = 1
:指定数据库的版本号,当实体或数据库结构发生变化时,需要增加这个版本号 -
exportSchema = false
:指定不导出数据库的 schema
(2)思路解析
-
单例模式确保整个应用程序中只有一个 MyDatabase 实例对象
-
通过
Room.databaseBuilder
方法构建数据库,该方法需要三个参数,分别为上下文、数据库类、数据库名称 -
定义获取 StaffDao 实例对象的抽象方法,Room 将自动生成这个方法的实现
4、Repository 解析
package com.my.room.repository;
import android.os.AsyncTask;
import com.my.room.dao.StaffDAO;
import com.my.room.database.MyDatabase;
import com.my.room.entity.Staff;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.QueryAllObserver;
import com.my.room.repository.observer.UpdateObserver;
import java.util.List;
public class StaffRepository {
private MyDatabase myDatabase;
private StaffDAO staffDAO;
public StaffRepository() {
myDatabase = MyDatabase.getInstance();
staffDAO = myDatabase.getStaffDAO();
}
// ====================================================================================================
public void insert(Staff staff, InsertObserver insertObserver) {
new InsertAsyncTask(staff, insertObserver).execute();
}
public void delete(Staff staff, DeleteObserver deleteObserver) {
new DeleteAsyncTask(staff, deleteObserver).execute();
}
public void update(Staff staff, UpdateObserver updateObserver) {
new UpdateAsyncTask(staff, updateObserver).execute();
}
public void queryAll(QueryAllObserver<Staff> queryAllObserver) {
new QueryAllAsyncTask(queryAllObserver).execute();
}
// ====================================================================================================
private class InsertAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {
private Staff staff;
private InsertObserver insertObserver;
public InsertAsyncTask(Staff staff, InsertObserver insertObserver) {
this.staff = staff;
this.insertObserver = insertObserver;
}
@Override
protected RepositoryItem doInBackground(Void... voids) {
try {
staffDAO.insert(staff);
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem repositoryItem) {
super.onPostExecute(repositoryItem);
if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
insertObserver.onSuccess();
} else {
insertObserver.onError(repositoryItem.getMsg());
}
}
}
private class DeleteAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {
private Staff staff;
private DeleteObserver deleteObserver;
public DeleteAsyncTask(Staff staff, DeleteObserver deleteObserver) {
this.staff = staff;
this.deleteObserver = deleteObserver;
}
@Override
protected RepositoryItem<Object> doInBackground(Void... voids) {
try {
staffDAO.delete(staff);
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
super.onPostExecute(repositoryItem);
if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
deleteObserver.onSuccess();
} else {
deleteObserver.onError(repositoryItem.getMsg());
}
}
}
private class UpdateAsyncTask extends AsyncTask<Void, Void, RepositoryItem<Object>> {
private Staff staff;
private UpdateObserver updateObserver;
public UpdateAsyncTask(Staff staff, UpdateObserver updateObserver) {
this.staff = staff;
this.updateObserver = updateObserver;
}
@Override
protected RepositoryItem<Object> doInBackground(Void... voids) {
try {
staffDAO.update(staff);
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, null);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem<Object> repositoryItem) {
super.onPostExecute(repositoryItem);
if (repositoryItem.getCode() == RepositoryItem.CODE_SUCCESS) {
updateObserver.onSuccess();
} else {
updateObserver.onError(repositoryItem.getMsg());
}
}
}
private class QueryAllAsyncTask extends AsyncTask<Void, Void, RepositoryItem<List<Staff>>> {
private QueryAllObserver<Staff> queryAllObserver;
public QueryAllAsyncTask(QueryAllObserver<Staff> queryAllObserver) {
this.queryAllObserver = queryAllObserver;
}
@Override
protected RepositoryItem<List<Staff>> doInBackground(Void... voids) {
try {
List<Staff> staffs = staffDAO.queryAll();
return new RepositoryItem(RepositoryItem.CODE_SUCCESS, null, staffs);
} catch (Exception e) {
e.printStackTrace();
return new RepositoryItem(RepositoryItem.CODE_ERROR, e.getMessage(), null);
}
}
@Override
protected void onPostExecute(RepositoryItem<List<Staff>> repositoryItem) {
super.onPostExecute(repositoryItem);
queryAllObserver.onResult(repositoryItem.getData());
}
}
}
- StaffRepository 类是对 StaffDAO 的封装,封装的主要目的是异步任务处理
(1)异步任务处理
- 对于每种 StaffDAO 实例对象的操作都定义了一个继承 AsyncTask 的内部类,它用于安排操作在后台线程中执行,然后使用观察者模式来通知调用者
-
InsertAsyncTask:负责在后台线程中执行插入操作,完成后(无论成功还是失败)通过 InsertObserver 通知调用者
-
DeleteAsyncTask:负责在后台线程中执行删除操作,完成后(无论成功还是失败)通过 DeleteObserver 通知调用者
-
UpdateAsyncTask:负责在后台线程中执行插入操作,完成后(无论成功还是失败)通过 UpdateObserver 通知调用者
-
QueryAllAsyncTask:负责在后台线程中执行查询操作,完成后(无论成功还是失败)通过 QueryAllObserver 通知调用者
(2)补充
-
线程调度:数据库操作不能在 UI 线程执行,操作在后台线程中(doInBackground 方法)执行完成后,然后回到 UI 线程中(onPostExecute 方法)执行后续操作
-
异常处理:在异步任务种,捕获可能抛出的异常,例如,执行插入操作时主键冲突会抛出异常