美文网首页AndroidJetpack
Room的使用以及数据库的升级

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

作者: 因为我的心 | 来源:发表于2020-08-04 18:24 被阅读0次

    一、前言:

    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

    参考:

    相关文章

      网友评论

        本文标题:Room的使用以及数据库的升级

        本文链接:https://www.haomeiwen.com/subject/srmsrktx.html