Android随笔-Room简单使用

概述

处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的使用场景是缓存相关的数据,这样一来,当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容。

Room 持久性库在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite
的强大功能的同时,能够流畅地访问数据库。具体来说,Room 具有以下优势:

针对 SQL 查询的编译时验证。 可最大限度减少重复和容易出错的样板代码的方便注解。 简化了数据库迁移路径。

Room用于数据持久化,使用简单,对SQL不了解也可以快速使用,但暴露出来的api有限,遇到复杂的数据还需使用SQL。Room本身性能并无特别强大之处,但是在Androidx中配合了Lifecycle,可以感知生命周期是其他数据库无法比拟的;同时搭配WorkManager使用可以实现非即时(至少15分钟之后),且一定会实现的任务(APP没有被删除的情况下)(目前仅限于Google pixel)。本文只对Room的简单使用进行阐述,不涉及Lifecycle等其他内容。

组成

Room 包含三个主要组件:

  • Database:数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。
  • Entity:数据实体,用于表示应用的数据库中的表。
  • DAO:数据访问对象 ,提供您的应用可用于查询、更新、插入和删除数据库中的数据的方法。

数据库类为应用提供与该数据库关联的 DAO 的实例。反过来,应用可以使用 DAO 从数据库中检索数据,作为关联的数据实体对象的实例。此外,应用还可以使用定义的数据实体更新相应表中的行,或者创建新行供插入。这里加了一层Repository用于处理网络数据和本地数据。
Android随笔-Room简单使用_第1张图片
具体的流程如下:
Android随笔-Room简单使用_第2张图片

使用

1. 添加依赖

dependencies {
    ...
    // 数据持久化room
    implementation 'androidx.room:room-runtime:2.4.2'
    annotationProcessor 'androidx.room:room-compiler:2.4.2'
}

2. 添加数据库schemas(选)

    defaultConfig {
        minSdkVersion MinSdkVersion
        targetSdkVersion TargetSdkVersion
        versionCode 1
        versionName "1.0"
        
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName(),
                             "room.schemaLocation":"$projectDir/schemas".toString()]
            }
        }
    }

3. 创建Entity

@Entity(tableName = "user")
public class User {
    // 主键,自增
    @PrimaryKey(autoGenerate = true)
    @NonNull
    public Integer id;
    // 名称
    @ColumnInfo(name = "name")
    public String name;
    // 年龄
    public Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
  • @Entity:表示User是一张数据表
  • tableName:数据库表名,不写时默认为“”
  • @primaryKeys:主键
  • autoGenerate:默认false,为true时表示主键无需赋值,值会自动递增
  • @ColumnInfo:字段属性,name为该属性在数据库中字段名称,不写默认为属性名称

除此之外还有foreignKeys:外键、indices:索引、defaultValue:默认值等,具体使用还需自行查阅。

4. 创建DAO

public interface UserDao {

    /**
     * 查询所有用户
     *
     * @return
     */
    @Query("SELECT * FROM user")
    List<User> queryAllUser();

    /**
     * 根据名称查询所有用户
     *
     * @return
     */
    @Query("SELECT * FROM user WHERE name = :name")
    List<User> queryUserByName(String name);

    /**
     * 插入用户
     *
     * @param user
     */
    @Insert
    void insertCustom(User user);

    /**
     * 更新用户
     *
     * @param users
     */
    @Update
    void updateUser(User... users);

    /**
     * 删除用户
     *
     * @param user
     */
    @Delete
    void deleteUser(User user);

    /**
     * 清空数据
     */
    @Query("DELETE FROM user")
    void deleteAllUsers();

}
  • @Query:查
  • @Insert:增
  • @Update:改
  • @Delete:删

具体需求根据业务来,可以批量操作,也可单一操作,若有些操作无法实现的可用@Query+SQL语句进行操作。批量操作时Room内部使用了事务,无需用户单独控制。

5. 创建Database

@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class UserDatabase extends RoomDatabase {
    // 数据库名称
    private static final String DATABASE_NAME = "user_db";
    // 数据库实例
    private static UserDatabase INSTANCE;

    // 单例
    public static UserDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (UserDatabase.class) {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, DATABASE_NAME)
                        .fallbackToDestructiveMigration()// 暴力升级,可能会出现数据丢失
                        .build();
            }
        }
        return INSTANCE;
    }


    // 暴露操作对象
    public abstract UserDao userDao();
}

  • @Database:数据库,需要指定entities
  • version:数据库版本,一般只能升不能降,表格有所改动均需要升级
  • exportSchema:数据配置,默认为true,需要进行第二步配置,若false,则无需配置
  • fallbackToDestructiveMigration:暴力升级,可能会出现数据丢失

Room.databaseBuilder还有其他配置,根据需要进行配置。

6. 创建异步任务

数据库操作可能会耗时,建议创建异步任务进行操作。

public class QueryUserTask extends AsyncTask<Void, Void, List<User>> {
    private UserDao userDao;
    
    public QueryUserTask(UserDao userDao) {
        this.userDao = userDao;
    }

    @Override
    protected List<User> doInBackground(Void... voids) {
        Log.d("QueryUserTask", "doInBackground: start");
        List<User> users = userDao.queryAllUser();
        Log.d("QueryUserTask", "doInBackground: end");
        return users == null ? Collections.EMPTY_LIST : users;
    }
}

AsyncTask为异步任务,Java默认是同步任务,异步任务需要单独创建。

public abstract class AsyncTask<Params, Progress, Result> {...}
  • Params:参数类型,无参使用Void
  • Progress:任务进度,不需要使用Void
  • Result:返回类型,没有返回值使用Void

7. 创建仓库Repository

public class UserRepository {
    private UserDao userDao;
    private Context context;

    public UserRepository(Context context) {
        this.context = context.getApplicationContext();
        UserDatabase centerDataBase = UserDatabase.getInstance(context.getApplicationContext());
        userDao = centerDataBase.userDao();
    }
    
    /**
     * 查询所有用户
     *
     * @return
     */
    public List<User> queryUser() {
        if (userDao == null) {
            Log.e("UserRepository", "QueryUser: userDao is null");
            return Collections.EMPTY_LIST;
        }
        List<User> users = new QueryUserTask(userDao).doInBackground();
        return users;
    }
}

使用Repository是为了专门处理本地缓存数据和网络数据,统一数据。

8. 调用

public class MainActivity extends AppCompatActivity {

    private TextView query;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        query = findViewById(R.id.tv_query_all);

        // 触发
        query.setOnClickListener(v -> queryUser());
    }

    private void queryUser(){
        // 子线程
        new Thread(() -> {
            UserRepository userRepository = new UserRepository(getApplicationContext());
            // 查询用户
            List<User> users = userRepository.queryUser();
            // 更新ui
            mHandler.sendEmptyMessage(users.size());
        }).start();
    }


    private Handler mHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            query.setText("已查询" + msg.what + "条数据");
        }
    };
}

数据库操作时需要在子线程中进行,操作完成后若需更新UI,则需切换到主线程进行UI刷新。若是数据库操作比较耗时,在主线程中操作,容易出现ANR。若一定要在主线程进行操作,则在UserDatabase实例时,需要调用allowMainThreadQueries():

    // 单例
    public static UserDatabase getInstance(Context context) {
        if (INSTANCE == null) {
            synchronized (UserDatabase.class) {
                INSTANCE = Room.databaseBuilder(context.getApplicationContext(), UserDatabase.class, DATABASE_NAME)
                        .fallbackToDestructiveMigration()// 暴力升级,可能会出现数据丢失
                        .allowMainThreadQueries()// 允许在主线程操作
                        .build();
            }
        }
        return INSTANCE;
    }

这里使用了Handler进行线程切换,msg.what一般用户区分消息类型,若需要传递数据需要使用Message包裹对象,不可像代码中那样使用,代码中使用msg.what传递消息内容是为了简单方便,但显得不专业,不建议使用。主线程更新也可使用runOnUiThread(Runnable action),原理是一样的。
一般逻辑操作是放在ViewModel中进行,通常还有DataBinding,这里为了简单就直接在Activity中进行。

总结

Room使用看似有些繁琐,但相比于Android原生的SQLite还是简单不少,若是数据操作比较复杂,还是建议使用SQLite或其他三方库。

你可能感兴趣的:(Android,android,sqlite,数据库)