这是我参与「第四届青训营 」笔记创作活动的第16天。
本文主要对Room内容做出流程性梳理。(例子为Java语言
参考文章使用 Room 将数据保存到本地数据库 | Android 开发者 | Android Developers (google.cn) Android_存储之DataBase之Room - 流浪_归家 - 博客园 (cnblogs.com)
概述
Room持久性库是在SQLite基础上提供的一个抽象库,让用户能够在充分利用SQLite的强大功能的同时,获享更强健的数据库访问机制。(当设备无法访问网络时,用户仍然可以在离线状态下浏览该内容)
Room优势
- 针对SQL查询的编译时验证
- 最大限度减少重复和容易出错的样板代码的方便注解
- 简化了数据库迁移路径 该文写于22.8.16,目前最近更新时间为22年7月27日,版本2.4.3
Room组件
- 数据库类,用于保存数据库并作为应用持久性数据底层连接的主要访问点。
- 数据实体,勇于表示应用的数据库中的表。
- 数据访问对象(DAO),提供项目应用可用于查询、更新、插入和删除数据库中的数据的方法。
过程
添加依赖
def room_version = "2.4.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:roomcompiler:$room_version"
教程中还出现的以下关于Kotlin的内容可以自行删去
建立实体类
package com.example.room;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
//数据库表名称:User(默认类名,有tableName就是tableName
//@Entity定义对象将会成为被JPA管理的实体,将映射到指定的数据库表。
@Entity(tableName = "User")
public class User {
@PrimaryKey
public int uid;
//与数据表名同理
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
注意点:
- SQLite中的表和列名称不区分大小写
- @PrimaryKey主键,如果需要Room为实体分配自动ID,将该注解的autoGenerate属性设置为true
- 复合主键:
@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
public String firstName;
public String lastName;
}
- 忽略字段
默认Room会为实体类中定义的每个字段创建一个列,如果有不想保留的字段,则可以用
@Ignore为这些字段添加注释
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
- 数据唯一性
@Entity(indices = {@Index(value = {"first_name", "last_name"},
unique = true)})
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
保证该表中没有完全相同的名字的两个人 5. 特定列编入索引
@Entity(indices = {@Index("name"),
@Index(value = {"last_name", "address"})} )
public class User {
@PrimaryKey
public int id;
public String firstName;
public String address;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
加快查询速度
Dao访问数据
@Dao
public interface UserDao {
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
@Query("SELECT * FROM user")
List<User> getAll();
}
两种类型的DAO方法可以定义数据库交互:
- 在不编写任何SQL代码的情况下插入、更新和删除数据中行
- 自行编写与数据库交互的SQL查询方法。
- Insert
@Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);
@Insert
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
- Update
@Dao
public interface UserDao {
@Update
public void updateUsers(User... users);
}
原理:Room使用主键将传递的实体实例与数据库中的行进行匹配。如果没有具有相同主键的行,则不会进行修改。Update方法可以选择是否返回int值,该值代表更新成功的行数。 3. Delete 与Insert类似,该方法接受数据实体实例作为参数。
@Dao
public interface UserDao {
@Delete
public void deleteUsers(User... users);
}
原理:Room使用主键将传递的实体实例与数据库中的行进行匹配,如果没有具有相同主键的行,Room不会进行任何更改。该方法可以选择返回int值,代表删除的行数。 4. Query 注意:Room会在编译时验证SQL查询。如果该查询出现问题,会出现编译错误,而不是运行时错误。
- 使用注解,编写SQL语句并将其作为DAO方法公开
@Query("SELECT * FROM user")
public User[] loadAllUsers();
- 只将需要的数据返回 定义需要的列组成的类
public class NameTuple {
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
@NonNull
public String lastName;
}
从查询方法返回该简单对象
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
ps:如果查询返回的列未映射到返回的对象中的字段,则 Room 会显示一条警告。
创建数据库类UserDataBase
package com.example.room;
import androidx.room.Database;
import androidx.room.RoomDatabase;
//@Database注释定义数据库类, entities指明包含的实体,version表明版本 数据迁移一定需要,后面有讲到
@Database(entities = {User.class}, version = 1)
public abstract class UserDataBase extends RoomDatabase {
public abstract UserDao userDao();
}
运行在Android设备上的JUnit测试
错误1
only buildscript {} and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed
位置错误,plugins{}块之前,只允许buildscript{}和其他插件{}脚本块。