一、前言:
Room 是一个对象关系映射(ORM)库。可以很容易将 SQLite 表数据转换为 Java 对象。Room 在编译时检查 SQLite 语句。
Room 为 SQLite 提供一个抽象层,以便在充分利用 SQLite 的同时,可以流畅地进行数据库访问。
1.1 添加依赖
如果想使用 Room,需要你的 APP 或者 module 的 build.gradle 中添加以下依赖:
dependencies {
//room
def room_version = "2.2.3"
implementation "androidx.room:room-runtime:$room_version"
// For Kotlin use kapt instead of annotationProcessor (注意这个注释)
annotationProcessor "androidx.room:room-compiler:$room_version"
//下面是可选的
implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
}
前面的两句是必须的,后面的部分为可选的。
1.2 Room 组件
Room 有 3 个主要的组件:
-
Database:包含数据库持有者,并作为与 App 持久关联数据的底层连接的主要访问点。
用 @Database 注解的类应满足以下条件:
1、是一个继承至 RoomDatabase 的抽象类。
2、 在注解中包含与数据库相关联的实体列表。
3、包含一个具有 0 个参数的抽象方法,并返回用 @Dao 注解的类。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 实例。 Entity:表示数据库内的表(Table)。
DAO:包含用于访问数据库的方法。
1.3 Room 各组件间关系
Room 的大致使用方法如下:
- App 通过 Room 的 Database 获取与数据库相关的数据库访问对象(DAO)。
- 然后,App 使用 DAO 从数据库中获取 Entity,并且将 Entity 的变化保存到数据库中。
- 最后,APP 使用 Entity 获取和设置数据库中表的数据。
Room 中各组件之间的关系如图-1 所示:
二、Entity(实体)
在使用 Room 持久化库(Room persistence library)时,需要将相关字段集定义为 Entity。对于每一个 Entity,在与其相关的 Database 对象中会创建一个表(Table)。必须通过 Database 类的 entities 数组引用这个 Entity 类。
下面的代码片段展示如何定义 Entity:
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
要持久化一个字段(Field),Room 必须能够使用它。可以将字段设置为 public,也可以为它提供 getter 和 setter 方法。在提供 getter 和 setter 方法时,需要遵守 Room 中的 JavaBeans 协议。
2.1 设置 Table 名称
Room 默认使用类名作为数据库的 Table 名称。可以通过 @Entity 的 tableName 属性设置 Table 的名称。(注意:在 SQLite 中,Table 名称是不区分大小写的。)
@Entity(tableName = "users")
public class User {
// ...
}
2.2 设置列名
Room 使用字段(Filed)名称作为在数据库中的默认列名。可以通过给 Filed 添加 @ColumnInfo 注解设置列名。
@Entity(tableName = "users")
public class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
2.3 设置主键
每个 Entity 必须设置至少一个 Field 作为主键(primary key)。即使只有 1 个 Field,也需要将其设置为主键。有两种方法设置主键:
使用注解 @PrimaryKey,可以用来设置单个主键。
@Entity
public class User {
@PrimaryKey(autoGenerate = true)
@NonNull
public String firstName;
public String lastName;
}
如果需要 Room 自动分配 IDs 给 Entity,可以设置 @PrimaryKey 的 autoGenerate 属性。
- 使用注解 @Entity 的 primaryKeys 属性,可以用来设置单个主键和复合主键。
@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
@NonNull
public String firstName;
@NonNull
public String lastName;
}
2.4 设置忽略字段(Ignore fields)
默认情况下,Room 为 Entity 中每个 Field 创建一列。如果在 Entity 中存在不需要持久化的 Field,可以给它们添加 @Ignore 注解。
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
如果子类不需要持久化父类中的 Field,使用 @Entity 的 ignoredColumns 属性更为方便。
@Entity(ignoredColumns = "picture")
public class RemoteUser extends User {
@PrimaryKey
public int id;
public boolean hasVpn;
}
三、DAO(Data access object)
1、在 Room 持久化库中,使用数据访问对象(data access objects, DAOs)访问 App 的数据。Dao 对象集合是 Room 的主要组件,因为每个 DAO 提供访问 App 的数据库的抽象方法。
2、通过使用 DAO 访问数据库,而不是通过查询构造器或直接查询,可以分离数据库架构的不同组件。此外,在测试应用时,DAOs 可以轻松模拟数据库访问。
3、DAO 可以是接口(interface),也可以是抽象类(abstract class)。如果是一个抽象类,可以有一个构造函数,其只接收一个 RoomDatabase 参数。在编译时,Room 为每个 DAO 创建具体实现。
注意:除非在构造器上调用 allowMainThreadQueries(),否则 Room 不支持在主线程上进行数据库访问,因为它可能会长时间锁定 UI。不过异步查询(返回 LiveData 或 Flowable 实例的查询)不受此规则约束,因为它们在需要时会在后台线程进行异步查询。
3.1 插入(Insert)
当创建 DAO 方法并使用 @Insert 对其进行注解时,Room 将生成一个实现,在单个事务中将所有参数插入数据库中。
@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);
}
当使用 @Insert 注解的方法仅仅只有一个参数时,可以返回一个 long 类型的值,其表示插入项的 rowId。如果参数是一个数组或集合,则返回 long[] 或 List<Long> 类型的值。
3.2 更新(Update)
更新方法修改数据库中的一组 Entity(由参数提供)。使用每个 Entity 的主键进行匹配查询。
@Dao
public interface UserDao {
@Update
public void updateUsers(User... users);
}
更新方法可以返回一个 int 类型的值,其表示数据库中更新的行数,不过通常是不需要的。
3.3 删除(Delete)
删除函数移除数据库中的一组 Entity(由参数提供)。使用实体的主键进行匹配。
@Dao
public interface UserDao {
@Delete
public void deleteUsers(User... users);
}
和更新方法一样,删除方法也可以返回一个 int 类型的值,其表示数据库中删除的行数,通常也是不需要的。
3.4 查询(Query)
@Query 是 DAO 类中的重要注解。它允许在数据库上执行读写操作。每个 @Query 方法都是在编译时验证的;因此,如果存在查询问题,将出现编译错误而不是运行时错误。
在编译时,Room 还验证查询的返回值,如果返回对象中的字段名称与查询中的相应列名称不匹配,将通过以下两种方式之一告知:(在下面 3.4.3 返回列的子集 会提到)
- 如果仅仅部分 Field 名称匹配,将显示 Warning。
- 如果没有 Field 名称匹配,将显示 Error。
3.4.1 简单查询
下面是一个简单的查询,获取所有 User。
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}
在编译时,Room 知道查询 user 表中的所有列。如果这个查询存在语法错误,或者数据库中不存在 user 表,Room 将显示相应的错误。
3.4.2 带参数的查询
大多数情况下,需要将参数传递到查询中以执行筛选操作,例如仅需要显示大于某一年龄的 User。这时,我们可以使用方法参数。
@Dao
public interface UserDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
在编译时,Room 使用 minAge 方法参数匹配 :minAge 绑定参数。如果存在匹配错误,将出现编译错误。
还可以在查询中传递多个参数或者多次引用它们。
@Dao
public interface UserDao {
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);
@Query("SELECT * FROM user WHERE first_name LIKE :search " +
"OR last_name LIKE :search")
public List<User> findUserWithName(String search);
}
在查询时,传递的参数还可以是一个集合。Room 知道参数何时是一个集合,并根据提供的参数数量在运行时自动展开。
@Dao
public interface UserDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
3.4.3 返回列的子集
大多数情况下,我们可能只需要获取一个 Entity 中的几个 Field。这样可以节省宝贵的资源,并且可以更快速地完成查询。
只要结果列集合可以映射到返回的对象中,Room 允许返回任何基于 Java 的对象。例如,可以创建以下普通的 Java 对象(plain old Java-based object, POJO)来获取用户的 first name 和 last name:
public class NameTuple {
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
@Dao
public interface UserDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}
如果查询结果返回太多列,或者一列在 NameTuple 中不存在,Room 将显示一个警告。
- 注意:POJO 也可是使用 @Embedded 注解。
3.4.4 可观察的查询
如果希望 App 的 UI 在数据发生变化时自动更新 UI,可以在查询方法中返回一个 LiveData 类型的值。Room 会产生所有必须的代码,用于在数据库发生变化时更新这个 LivaData 对象。
@Dao
public interface UserDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
3.4.5 RxJava 的响应式查询
Room 支持返回一下 RxJava2 类型的值:
-
@Query
方法:支持返回Publisher
、Flowable
和Observable
类型的值。 -
@Insert
、@Update
和@Delete
方法:Room 2.1.0 及以上版本支持返回Completable
、Single<T>
和Maybe<T>
类型的值。
需要在 App 的 build.gradle
文件中添加对最新 rxjava2 版本的依赖:
dependencies {
implementation 'androidx.room:room-rxjava2:2.1.0-alpha02'
}
点击 这里 查看更详细的信息。
3.4.6 直接 Cursor 访问
查询的返回值可以是 Cursor 对象。
@Dao
public interface UserDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
- 注意:强烈建议不要使用这种方式。
3.4.7 多表查询
Room 允许进行多表查询。如果返回的是可观察的数据类型(例如 Flowable 或 LivaData),Room 将监控所有在查询中引用的表,用于刷新数据。
@Dao
public interface UserDao {
@Query("SELECT * FROM book " +
"INNER JOIN loan ON loan.book_id = book.id " +
"INNER JOIN user ON user.id = loan.user_id " +
"WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);
}
四、Database
在 Room 持久化库中,通过 @Database
类访问数据库。
4.1 定义 Database
下面的代码片段展示如何定义 Database:
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
如果直接按照上面的写法,会出现以下错误信息:
警告: Schema export directory is not provided to the annotation processor so we cannot export the schema. You can either provide `room.schemaLocation` annotation processor argument OR set exportSchema to false.
上面的错误信息已经提供了两种解决方法:
-
给 RoomDatabase 设置 exportSchema = false。
@Database(entities = {User.class}, version = 1, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract UserDao userDao(); }
-
在你的 APP 或者 module 的
build.gradle
中添加以下注解信息:android { ... defaultConfig { ... //指定room.schemaLocation生成的文件路径 javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } } } }
4.2 获取数据库实例
可以通过以下方法获取创建的数据库的实例:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
其中 database-name 为你自己定义的数据库名称,比如 RoomSample.db。因为其会占用较多的资源,所以一般建议使用单例模式。
五、Room 数据库迁移
在 Room 持久化库中通过使用 Migration 类保存用户数据。每个 Migration 类指定起始版本和结束版本。在运行时,Room 运行每个 Migration 类的 migrate() 方法,使用正确的顺序将数据库迁移到后面的版本。
1、Android提供了一个名为Migration的类,来完成Room的升级。
public Migration(int startVersion, int endVersion)
2、Migration有两个参数,startVersion和endVersion。startVersion表示当前版本(手机上安装的版本),endVersion表示将要升级到的版本。如果你的手机中的应用程序数据库的版本为1,那么下方Migration会将你的数据库版本从1升级到2。
static final Migration MIGRATION_1_2 = new Migration(1, 2)
{
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
{
//执行升级相关操作
}
};
以此类推,如果你的数据库需要从2升级到3,则需要写这样一个Migration。
private static Migration MIGRATION_2_3 = new Migration(2, 3)
{
@Override
public void migrate(@NonNull SupportSQLiteDatabase database)
{
//执行升级相关操作
}
};
注意:如果用户手机上安装的应用程序数据库版本为1,而当前要安装的应用程序数据库版本为3,这种情况该怎么办呢?这种情况下,Room会先判断当前有没有从1->3的Migration升级方案,如果有,就直接执行从1->3的升级方案,如果没有,那么Room会按照顺序先后执行Migration(1, 2)->Migration(2, 3)以完成升级。
写好Migration之后,我们还需要通过addMigrations()方法,将升级方案添加到Room。
Room.databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_1_3)
.build();
3、异常处理
在更新数据库的模式(schema)后,一些设备上的数据库可能仍然是旧的模式版本。如果 Room 无法找到将设备的数据库从旧版本升级到当前版本的迁移规则,将出现 IllegalStateException。
为了防止这种情况发生时应用崩溃,在创建数据库时调用 fallbackToDestructiveMigration() 方法,这样 Room 将会重建应用的数据库表(将直接删除原数据库表中的所有数据)。
Room.databaseBuilder(context.getApplicationContext(), MyDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_1_3)
.build();
这种破坏性的恢复逻辑包括几个额外的选项:
- 仅当数据库从问题版本进行迁移时使用回退逻辑,使用 fallbackToDestructiveMigrationFrom()。
- 仅当尝试模式降级时执行破坏性重建,使用 fallbackToDestructiveMigrationOnDowngrade()。
六、 使用 Room 引用复杂数据
Room 提供了在基本类型和盒式类型之间转换的功能,但不允许 Entity 之间的对象引用。
6.1 使用类型转换器
有时,希望将自定义的数据类型的值存储在数据库的单个列中。为了支持自定义类型,需要提供一个 TypeConverter
,它将自定义类型转换为 Room 能够持久化的已知类型。
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
由于 Room 已经知道如何持久化 Long
对象,所以它能使用这个转换器来持久化 Data
类型的数据。
接下来,为 AppDatabase
添加 @TypeConverters
注解,以便 Room 能使用为 Entity 和 DAO 定义的转换器。
@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
使用这些转换器后,在查询中可以像使用基本类型一样使用自定义类型。
@Entity
public class User {
private Date birthday;
}
@Dao
public interface UserDao {
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List<User> findUsersBornBetweenDates(Date from, Date to);
}
6.2 理解为什么 Room 不允许对象引用
重要信息:Room 不允许 Entity 类之间的对象引用。而应该显式地请求 App 需要的数据。
将数据库与相应对象模型建议映射关系是一种常见的做法,在服务器端非常有效。即使程序在访问 Field 时加载它们,服务器端仍然表现良好。
但是,在客户端,这种类型的延迟加载是不可行的。因为通常这一过程发生在 UI 线程,在 UI 线程上查询磁盘上的信息会造成严重的性能问题。UI 线程通常有大约 16ms 的时间来计算和绘制 Activity 的更新布局,即使一个查询只需要 5ms,App 可能仍然不够时间绘制帧,从而导致明显的视觉延迟。如果有一个单独的事务并行运行,或者设备正在运行其他磁盘密集型任务,那么查询操作可能花费更多时间。然而,如果不使用延迟加载,App 将获取比它实际需要更多的数据,从而出现内存消耗问题。
对象关系映射通过让开发人员做这个决定,这样他们能够为 App 用户事例做出最好的选择。开发人员通常选择在 App 和 UI 之间共享模型。然而,这种方案的扩展性很差;因为当 UI 发生变化时,这种共享模型将出现难以预料和调试的问题。
例如,考虑一个加载 Book
对象列表的 UI,每一个 book
持有一个 Author
对象。最初,可能会使用延迟加载进行查询,以便让 Book
实例检索 author
。第一次检索 author
时,进行查询数据库操作。后来,需要在 App 的 UI 中显示 author 名称。可以很容易地访问这个名称,如下面的代码片段所示:
authorNameTextView.setText(book.getAuthor().getName());
然后,这种看似无害的更改会导致在主线程上查询 Author
表。
如果你提前查询 author
信息,则在不再需要该数据时,很难更改数据的加载方式。例如,如果 App 的 UI 不再需要显示 Author
信息,那么 App 会加载不再显示的数据,从而浪费宝贵的内存空间。如果 Author
类引用其他表(比如 Books
),App 的效率会进一步降低。
要使用 Room 同时引用多个 Entity,需要创建一个包含每个 Entity 的 POJO,然后编写一个连接相应表的查询。这种结构良好的模型与 Room 强大的查询功能相结合,可让 App 在加载数据时消耗更少的资源,从而提高 App 的性能和用户体验。
七、代码
1、 MainActivity
public class MainActivity extends AppCompatActivity {
private User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn1 = findViewById(R.id.btn1);
Button btn2 = findViewById(R.id.btn2);
Button btn3 = findViewById(R.id.btn3);
Button btn4 = findViewById(R.id.btn4);
//创建User对象
user = new User(2, "小明", "北京朝阳区", 22);
/**
* 增加
*/
btn1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
for (int i = 3; i < 6; i++) {
//添加User用户
AppDatabase.getInstance().userDao().insertAll(new User(i, "小明", "北京朝阳区", 18));
}
//添加Book
AppDatabase.getInstance().bookDao().insertAll(new Book(1, "中华故事会", "上海市长宁区"));
}
});
/**
* 删
*/
btn2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//删除User用户
AppDatabase.getInstance().userDao().delete(user);
}
});
/**
* 改
*/
btn3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//更改User用户
user.setLastName("哈哈哈哈");
user.setFirstName("涛哥");
AppDatabase.getInstance().userDao().update(user);
}
});
/**
* 查
*/
btn4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//查询User用户
List<User> users = AppDatabase.getInstance().userDao().getUser("小明");
Log.d("LUO", "users=====" + users.size());
//查询Book
List<Book> bookList = AppDatabase.getInstance().bookDao().getAll();
Log.d("LUO", "bookList=====" + bookList.size());
}
});
}
}
2、User
@Entity
public class User extends BaseBean{
@PrimaryKey
private int id;
@ColumnInfo(name = "first_name")
private String firstName;
@ColumnInfo(name = "last_name")
private String lastName;
@ColumnInfo(name = "age")
private int age;
...
}
3、Book
@Entity
public class Book {
@PrimaryKey
private int id;
@ColumnInfo(name = "name")
private String name;
@ColumnInfo(name = "address")
private String address;
...
}
4、UserDao
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List<User> getAll();
@Query("select * from user where first_name = (:name)")
List<User> getUser(String name);
@Insert
void insertAll(User... users);
@Update
void update(User user);
@Delete
void delete(User user);
}
5、BookDao
@Dao
public interface BookDao {
@Query("SELECT * FROM book")
List<Book> getAll();
@Insert
void insertAll(Book... books);
@Delete
void delete(Book book);
@Update
void update(Book book);
}
6、AppDatabase
/**
* 数据库版本
*/
@Database(entities = {User.class, Book.class}, version = 2, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase INSTANCE;
private static final Object sLock = new Object();
public static AppDatabase getInstance() {
synchronized (sLock) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(BaseAplication.getContext(), AppDatabase.class, "room.db")
//崩溃后重建
.fallbackToDestructiveMigration()
//允许主线程访问数据库
.allowMainThreadQueries()
//升级
.addMigrations(MIGRATION_1_2)
.build();
}
return INSTANCE;
}
}
/**
* UserDao
* @return
*/
public abstract UserDao userDao();
/**
* BookDao
* @return
*/
public abstract BookDao bookDao();
/**
* 数据库升级
*/
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
// 为旧表添加新的字段
database.execSQL("ALTER TABLE user ADD age INTEGER Default 0 not null ");
//创建新的数据表
database.execSQL("CREATE TABLE IF NOT EXISTS `book` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `address` TEXT)");
}
};
}
7、BaseAplication
public class BaseAplication extends Application {
private static Context mContext;
@Override
public void onCreate() {
super.onCreate();
mContext = this;
}
public static Context getContext(){
return mContext;
}
}
8、指定room.schemaLocation生成的文件路径
defaultConfig {
applicationId "com.sumansoul.roomdemo"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
//指定room.schemaLocation生成的文件路径
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
八、Room 的优势
1、Room和SQLite对比:
在 Android 中,如果直接通过 SQLite API 实现数据持久化,需要实现以下操作:
- 需要创建一个任务繁重的 SQLiteOpenHelper 类,用于创建数据库以及数据库的升降级等。
- 需要创建维护表的字段的 Constant 类。
- 需要为数据库 CRUD 操作(create、read、update 和 delete)编写各种函数。
- 访问数据时需要对 Cursor 进行遍历操作。
相比之下,Room 作为在 SQLite 之上封装的 ORM 库,具备以下优势:
- 比 SQLite API 更简单的使用方式。
- 省略了许多重复代码。
- 能在编译时校验 SQL 语句的正确性。
- 数据库相关的代码分为 Entity,DAO,Database三个部分,结构清晰。
2、Room和GreenDao对比:
我们用图表来对比一下ORMLite、GreenDao和Room。对于insert操作,ORMLite由于在得到bind参数时使用反射,速度最慢,GreenDao使用事先生成的代码进行bind,但是其生成sql语句是通过字符串拼接,会有一点时间损耗,而Room则更彻底,连sql语句都为我们生成好。其性能最好。
update、get也是类似的,这里ORMLite由于没有updateList的方法,这里的时间还加上了list循环的开销。GreenDao都会调用SqlUtils的createSqlSelect和createSqlUpdate语句生成sql。只不过GreenDao的daoSession有缓存机制,直接从内存中查找。所以GreenDao的get有时候也会快于Room。
参考: