Bootstrap

2-1.Jetpack 之 Room 简单编码模板(Entity、DAO、Database、Repository)

一、Room

1、Room 概述
  • Navigation 是 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
  • User.java
package com.my.room.entity;

import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "User")
public class User {

    @PrimaryKey(autoGenerate = true)
    public Long id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "age")
    public Integer age;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
3、DAO
  • UserDAO.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.User;

import java.util.List;

@Dao
public interface UserDAO {

    @Insert
    void insert(User user);

    @Delete
    void delete(User user);

    @Update
    void update(User user);

    @Query("SELECT * FROM User")
    List<User> queryAll();
}
4、Database
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.UserDAO;
import com.my.room.entity.User;

@Database(entities = {User.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 UserDAO getUserDAO();
}
5、Repository Observer
  1. InsertObserver.java
package com.my.room.repository.observer;

public interface InsertObserver {
    void onSuccess();
    void onError(String msg);
}
  1. DeleteObserver.java
package com.my.room.repository.observer;

public interface DeleteObserver {
    void onSuccess();
    void onError(String msg);
}
  1. UpdateObserver.java
package com.my.room.repository.observer;

public interface UpdateObserver {
    void onSuccess();
    void onError(String msg);
}
  1. QueryAllObserver.java
package com.my.room.repository.observer;

import java.util.List;

public interface QueryAllObserver<T> {
    void onResult(List<T> ts);
}
6、Repository
  • UserRepository.java
package com.my.room.repository;

import android.os.AsyncTask;
import android.os.Handler;

import com.my.room.dao.UserDAO;
import com.my.room.database.MyDatabase;
import com.my.room.entity.User;
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 UserRepository {
    private MyDatabase myDatabase;
    private UserDAO userDAO;
    private Handler handler;

    public UserRepository() {
        myDatabase = MyDatabase.getInstance();
        userDAO = myDatabase.getUserDAO();
        handler = new Handler();
    }

    // ====================================================================================================

    public void insert(User user, InsertObserver insertObserver) {
        new InsertAsyncTask(user, insertObserver).execute();
    }

    public void delete(User user, DeleteObserver deleteObserver) {
        new DeleteAsyncTask(user, deleteObserver).execute();
    }

    public void update(User user, UpdateObserver updateObserver) {
        new UpdateAsyncTask(user, updateObserver).execute();
    }

    public void queryAll(QueryAllObserver<User> queryAllObserver) {
        new QueryAllAsyncTask(queryAllObserver).execute();
    }

    // ====================================================================================================

    private class InsertAsyncTask extends AsyncTask<Void, Void, Void> {

        private User user;
        private InsertObserver insertObserver;

        public InsertAsyncTask(User user, InsertObserver insertObserver) {
            this.user = user;
            this.insertObserver = insertObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                userDAO.insert(user);
                if (insertObserver != null) handler.post(() -> insertObserver.onSuccess());
            } catch (Exception e) {
                e.printStackTrace();
                if (insertObserver != null)
                    handler.post(() -> insertObserver.onError(e.getMessage()));
            }
            return null;
        }
    }

    private class DeleteAsyncTask extends AsyncTask<Void, Void, Void> {

        private User user;
        private DeleteObserver deleteObserver;

        public DeleteAsyncTask(User user, DeleteObserver deleteObserver) {
            this.user = user;
            this.deleteObserver = deleteObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                userDAO.delete(user);
                if (deleteObserver != null) handler.post(() -> deleteObserver.onSuccess());
            } catch (Exception e) {
                e.printStackTrace();
                if (deleteObserver != null)
                    handler.post(() -> deleteObserver.onError(e.getMessage()));
            }
            return null;
        }
    }

    private class UpdateAsyncTask extends AsyncTask<Void, Void, Void> {

        private User user;
        private UpdateObserver updateObserver;

        public UpdateAsyncTask(User user, UpdateObserver updateObserver) {
            this.user = user;
            this.updateObserver = updateObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                userDAO.update(user);
                if (updateObserver != null) handler.post(() -> updateObserver.onSuccess());
            } catch (Exception e) {
                e.printStackTrace();
                if (updateObserver != null)
                    handler.post(() -> updateObserver.onError(e.getMessage()));
            }
            return null;
        }
    }

    private class QueryAllAsyncTask extends AsyncTask<Void, Void, Void> {

        private QueryAllObserver<User> queryAllObserver;

        public QueryAllAsyncTask(QueryAllObserver<User> queryAllObserver) {
            this.queryAllObserver = queryAllObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                List<User> users = userDAO.queryAll();
                handler.post(() -> queryAllObserver.onResult(users));
            } catch (Exception e) {
                handler.post(() -> queryAllObserver.onResult(null));
            }
            return null;
        }
    }
}
7、Activity Layout
  • activity_user.java
<?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=".UserActivity"
    tools:ignore="MissingConstraints">

    <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_constraintHorizontal_bias="0.498"
        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_constraintHorizontal_bias="0.498"
        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_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn_update" />
</androidx.constraintlayout.widget.ConstraintLayout>
8、Activity Code
  • UserActivity.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.User;
import com.my.room.repository.UserRepository;
import com.my.room.repository.observer.DeleteObserver;
import com.my.room.repository.observer.InsertObserver;
import com.my.room.repository.observer.UpdateObserver;

public class UserActivity extends AppCompatActivity {

    public static final String TAG = UserActivity.class.getSimpleName();

    private UserRepository userRepository;

    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_user);

        userRepository = new UserRepository();

        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 -> {
            User user = new User();
            user.name = "jack";
            user.age = 20;

            userRepository.insert(user, 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 -> {
            User user = new User();
            user.id = 1L;

            userRepository.delete(user, 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 -> {
            User user = new User();
            user.id = 1L;
            user.name = "jack";
            user.age = 30;

            userRepository.update(user, 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 -> {
            userRepository.queryAll(users -> {
                if (users == null) {
                    Log.i(TAG, "------------------------------ queryAll - users is null");
                    return;
                }
                if (users.size() == 0) {
                    Log.i(TAG, "------------------------------ queryAll - users is empty");
                    return;
                }

                for (User user : users)
                    Log.i(TAG, "------------------------------ queryAll - " + user);
            });
        });
    }
}
Test
  • 增 -> 改 -> 查 -> 删 -> 查,输出结果
I/UserActivity: ------------------------------ insert success
I/UserActivity: ------------------------------ update success
I/UserActivity: ------------------------------ queryAll - User{id=1, name='jack', age=30}
I/UserActivity: ------------------------------ delete success
I/UserActivity: ------------------------------ queryAll - users is empty

三、Room 简单案例解析

1、Entity 解析
@Entity(tableName = "User")
public class User {

    @PrimaryKey(autoGenerate = true)
    public Long id;

    @ColumnInfo(name = "name")
    public String name;

    @ColumnInfo(name = "age")
    public Integer age;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • @Entity(tableName = "User"):指定 User 类为 Room 数据库中 User 表对应的实体类
  1. @PrimaryKey(autoGenerate = true):指定 id 字段为主键,并且它的值是自动生成的

  2. @ColumnInfo(name = "name"):指定 name 字段映射到表中的 name 列,@ColumnInfo(name = "age") 同理

  • User 表会在 APP 首次安装时由 Room 根据 User 类创建,SQL 语句如下
CREATE TABLE IF NOT EXISTS User (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    age INTEGER
);
2、DAO 解析
@Dao
public interface UserDAO {

    @Insert
    void insert(User user);

    @Delete
    void delete(User user);

    @Update
    void update(User user);

    @Query("SELECT * FROM User")
    List<User> queryAll();
}
  • @Dao:指定 UserDAO 接口与 Room 数据库进行交互,以操作 User 类,该接口使用 Room 相关注解来定义操作方法
  1. @Insert:插入数据,这里传递一个 User 对象作为参数,Room 将这个对象插入到数据库中

  2. @Delete:删除数据,这里传递一个 User 对象作为参数,Room 基于该对象的 id 字段来删除相应的行

  3. @Update:修改数据,这里传递一个 User 对象作为参数,Room 基于该对象的 id 字段来修改相应的行

  4. @Query("SELECT * FROM User"):查询数据,查询表中的所有行

3、Database 解析
@Database(entities = {User.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 UserDAO getUserDAO();
}
(1)注解解析
  • @Database:指定 MyDatabase 抽象类为 Room 数据库
  1. entities = {User.class}:指定这个数据库包含 User 类

  2. version = 1:指定数据库的版本号,当实体或数据库结构发生变化时,需要增加这个版本号

  3. exportSchema = false:指定不导出数据库的 schema

(2)思路解析
  1. 单例模式确保整个应用程序中只有一个 MyDatabase 实例对象

  2. 通过 Room.databaseBuilder 方法构建数据库,该方法需要三个参数,分别为上下文、数据库类、数据库名称

  3. 定义获取 UserDao 实例对象的抽象方法,Room 将自动生成这个方法的实现

4、Repository 解析
public class UserRepository {
    private MyDatabase myDatabase;
    private UserDAO userDAO;
    private Handler handler;

    public UserRepository() {
        myDatabase = MyDatabase.getInstance();
        userDAO = myDatabase.getUserDAO();
        handler = new Handler();
    }

    // ====================================================================================================

    public void insert(User user, InsertObserver insertObserver) {
        new InsertAsyncTask(user, insertObserver).execute();
    }

    public void delete(User user, DeleteObserver deleteObserver) {
        new DeleteAsyncTask(user, deleteObserver).execute();
    }

    public void update(User user, UpdateObserver updateObserver) {
        new UpdateAsyncTask(user, updateObserver).execute();
    }

    public void queryAll(QueryAllObserver<User> queryAllObserver) {
        new QueryAllAsyncTask(queryAllObserver).execute();
    }

    // ====================================================================================================

    private class InsertAsyncTask extends AsyncTask<Void, Void, Void> {

        private User user;
        private InsertObserver insertObserver;

        public InsertAsyncTask(User user, InsertObserver insertObserver) {
            this.user = user;
            this.insertObserver = insertObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                userDAO.insert(user);
                if (insertObserver != null) handler.post(() -> insertObserver.onSuccess());
            } catch (Exception e) {
                e.printStackTrace();
                if (insertObserver != null)
                    handler.post(() -> insertObserver.onError(e.getMessage()));
            }
            return null;
        }
    }

    private class DeleteAsyncTask extends AsyncTask<Void, Void, Void> {

        private User user;
        private DeleteObserver deleteObserver;

        public DeleteAsyncTask(User user, DeleteObserver deleteObserver) {
            this.user = user;
            this.deleteObserver = deleteObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                userDAO.delete(user);
                if (deleteObserver != null) handler.post(() -> deleteObserver.onSuccess());
            } catch (Exception e) {
                e.printStackTrace();
                if (deleteObserver != null)
                    handler.post(() -> deleteObserver.onError(e.getMessage()));
            }
            return null;
        }
    }

    private class UpdateAsyncTask extends AsyncTask<Void, Void, Void> {

        private User user;
        private UpdateObserver updateObserver;

        public UpdateAsyncTask(User user, UpdateObserver updateObserver) {
            this.user = user;
            this.updateObserver = updateObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                userDAO.update(user);
                if (updateObserver != null) handler.post(() -> updateObserver.onSuccess());
            } catch (Exception e) {
                e.printStackTrace();
                if (updateObserver != null)
                    handler.post(() -> updateObserver.onError(e.getMessage()));
            }
            return null;
        }
    }

    private class QueryAllAsyncTask extends AsyncTask<Void, Void, Void> {

        private QueryAllObserver<User> queryAllObserver;

        public QueryAllAsyncTask(QueryAllObserver<User> queryAllObserver) {
            this.queryAllObserver = queryAllObserver;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            try {
                List<User> users = userDAO.queryAll();
                handler.post(() -> queryAllObserver.onResult(users));
            } catch (Exception e) {
                handler.post(() -> queryAllObserver.onResult(null));
            }
            return null;
        }
    }
}
  • UserRepository 类是对 userDAO 的封装,封装的主要目的是异步任务处理
(1)异步任务处理
  • 对于每种 UserDAO 实例对象的操作都定义了一个继承 AsyncTask 的内部类,它用于安排操作在后台线程中执行,然后使用观察者模式来通知调用者
  1. InsertAsyncTask:负责在后台线程中执行插入操作,完成后(无论成功还是失败)通过 InsertObserver 通知调用者

  2. DeleteAsyncTask:负责在后台线程中执行删除操作,完成后(无论成功还是失败)通过 DeleteObserver 通知调用者

  3. UpdateAsyncTask:负责在后台线程中执行插入操作,完成后(无论成功还是失败)通过 UpdateObserver 通知调用者

  4. QueryAllAsyncTask:负责在后台线程中执行查询操作,完成后(无论成功还是失败)通过 QueryAllObserver 通知调用者

(2)补充
  1. 线程调度:数据库操作不能在 UI 线程执行,操作在后台线程中执行完成后,使用 Handler 来回到 UI 线程执行后续操作

  2. 异常处理:在异步任务种,捕获可能抛出的异常,例如,执行插入操作时主键冲突会抛出异常

;