Room的使用以及数据库的升级

一、前言:

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 所示:

图-1.png

二、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 方法:支持返回 PublisherFlowableObservable 类型的值。
  • @Insert@Update@Delete 方法:Room 2.1.0 及以上版本支持返回 CompletableSingle<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。

对比图.png

参考:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350