美文网首页android学习android相关Android开发
Room使用详解及常用数据库对比

Room使用详解及常用数据库对比

作者: Jerck_NING | 来源:发表于2020-12-31 14:01 被阅读0次

    Android Jetpack组件 —— Room使用详解及常用数据库对比

    一、 Room介绍

    • Room是Jetpack组件中一个对象关系映射(ORM)库。可以很容易将 SQLite 表数据转换为 Java 对象。
    • Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
    • 支持与LiveData、RxJava、Kotlin协成组合使用。
    • Google 官方强烈推荐使用Room。
    image.png

    二、 Room接入以及基础使用

    Room引用配置

    dependencies {
      def room_version = "2.2.5"
    
      implementation "androidx.room:room-runtime:$room_version"
      annotationProcessor "androidx.room:room-compiler:$room_version"
    
      // optional - RxJava support for Room
      implementation "androidx.room:room-rxjava2:$room_version"
    
      // optional - Guava support for Room, including Optional and ListenableFuture
      implementation "androidx.room:room-guava:$room_version"
    
      // optional - Test helpers
      testImplementation "androidx.room:room-testing:$room_version"
    
    }
    

    Room使用

    • 在使用数据的时候,需要主要涉及到Room三个部分:
      • Entity: 数据库中表对应的实体
      • Dao: 操作数据库的方法
      • DataBase: 创建数据库实例
    第一步 创建实体类
    @Entity(tableName = UserModel.USER_TABLE_NAME,
            indices = {@Index(value = {UserModel.FACE_ID}, unique = true),
                    @Index(value = {UserModel.NAME}, unique = true)})
    public class UserModel implements Parcelable {
    
        public static final String USER_TABLE_NAME = "user" ;
    
    
        public static final String NAME = "name";
        public static final String FACE_ID = "faceId";
    
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = BaseColumns._ID)
        public long id;
    
        @NonNull
        @ColumnInfo(name = FACE_ID)
        public String faceId;
    
        @NonNull
        @ColumnInfo(name = NAME)
        public String name;
    
        public UserModel(@NonNull String faceId, @NonNull String name) {
            this.faceId = faceId;
            this.name = name;
        }
       
    }
    
    • @Entity: 代表一个表中的实体,默认类名就是表名,如果不想使用类名作为表名,可以给注解添加表名字段@Entity(tableName = "user")
    • @PrimaryKey: 每个实体都需要自己的主键
    • @NonNull 表示字段,方法,参数返回值不能为空
    • @ColumnInfo(name = “faceId”) 如果希望表中字段名跟类中的成员变量名不同,添加此字段指明
    第二步 创建Dao
        @Dao
        public interface UserDao {
        
            @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                    UserModel.NAME + " = :name")
            LiveData<UserModel> queryByName2Lv(String name);
        
        
            @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                    UserModel.NAME + " = :name")
            UserModel queryByName2Model(String name);
        
        
            @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                    UserModel.FACE_ID + " = :faceId")
            LiveData<UserModel> queryByFaceId2Lv(String faceId);
    
            @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                    UserModel.FACE_ID + " = :faceId")
            UserModel queryByFaceId2Model(String faceId);
        
            @Query("SELECT COUNT(*) FROM " + UserModel.USER_TABLE_NAME)
            int count();
        
        
            @Query("SELECT * FROM " + UserModel.USER_TABLE_NAME)
            LiveData<List<UserModel>> queryAllByLv();
        
        
            @Query("SELECT * FROM " + UserModel.USER_TABLE_NAME)
            List<UserModel> queryAll();
        
            @Update
            public int updateUsers(List<UserModel> userModels);
        
            @Insert(onConflict = OnConflictStrategy.REPLACE)
            long insertUser(UserModel userModel);
        
            @Insert(onConflict = OnConflictStrategy.REPLACE)
            long[] insertAllUser(List<UserModel> userModels);
    
            @Delete
            void delete(UserModel... userModels);
    
            @Delete
            void deleteAll(List<UserModel> userModels);
    
            @Query("DELETE FROM " + UserModel.USER_TABLE_NAME + " WHERE " +
                    UserModel.FACE_ID + " = :faceId")
            int deleteByFaceId(String faceId);
    
        }
    
    • DAO是数据访问对象,指定SQL查询,并让他与方法调用相关联。
    • DAO必须是一个接口或者抽象类。
    • 默认情况下,所有的查询都必须在单独的线程中执行
    第三步 创建Database
    @Database(entities =  {
            UserModel.class
    },
            version = 1, exportSchema = true)
    public abstract class RoomDemoDatabase extends RoomDatabase {
    
        public abstract UserDao userDao();
    
        public static final String DATABASE_NAME = "room_demo";
    
        private static RoomDemoDatabase sInstance;
    
        public static RoomDemoDatabase getInstance(Context context) {
            if (sInstance == null) {
                synchronized (RoomDemoDatabase.class) {
                    if (sInstance == null) {
                        sInstance = buildDatabase(context);
                    }
                }
            }
            return sInstance;
        }
    
    
        private static RoomDemoDatabase buildDatabase(final Context appContext) {
            return Room.databaseBuilder(appContext, RoomDemoDatabase.class, DATABASE_NAME)
                    .allowMainThreadQueries()
    //                .openHelperFactory(new SafeHelperFactory("123456".toCharArray()))
                    .addCallback(new Callback() {
                        @Override
                        public void onCreate(@NonNull SupportSQLiteDatabase db) {
                            super.onCreate(db);
    
                        }
    
                        @Override
                        public void onOpen(@NonNull SupportSQLiteDatabase db) {
                            super.onOpen(db);
                        }
    
                    })
                    .build();
        }
    
    
    }
    
    • 创建一个抽象类继承自RoomDatabase

    • 给他添加一个注解@Database表名它是一个数据库,注解有两个参数第一个是数据库的实体,它是一个数组,可以传多个,当数据库创建的时候,会默认给创建好对应的表,第二个参数是数据库的版本号

    • 定义跟数据库一起使用的相关的DAO类

    • 创建一个RoomDemoDatabase的单例,防止同时打开多个数据库的实例

    • 使用Room提供的数据库构建器来创建该实例,第一个参数application,第二个参数当前数据库的实体类,第三个参数数据库的名字

    • exportSchema = true 支持导出Room生成的配置文件

            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
                }
            }
      

    三、 Room 数据库迁移

    添加新的实体类

    @Entity(tableName = FaceModel.FACE_TABLE_NAME,
            foreignKeys = {
                    @ForeignKey(entity = UserModel.class,
                            parentColumns = "faceId",
                            childColumns = "faceId",
                            onUpdate = ForeignKey.CASCADE,
                            onDelete = ForeignKey.CASCADE
                    )
            },
            indices = {@Index(value = {"faceId"})}
    )
    public class FaceModel  {
    
        public static final String FACE_TABLE_NAME = "face";
    
    
        public static final String TYPE = "type";
        public static final String FACE_ID = "faceId";
        public static final String PATH = "path";
    
    
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = BaseColumns._ID)
        public long id;
    
        @NonNull
        @ColumnInfo(name = PATH)
        public String path;
    
        @ColumnInfo(name = TYPE)
        public int type;
    
        @NonNull
        @ColumnInfo(name = FACE_ID)
        public String faceId;
    
        public FaceModel(@NonNull String path, int type, @NonNull String faceId) {
            this.path = path;
            this.type = type;
            this.faceId = faceId;
        }
    
    }
    

    配置实体类

    @Database(entities =  {
        UserModel.class, FaceModel.class
            },
        version = 2, exportSchema = true)
    

    添加Migration

    static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            Log.i(TAG, "migrate: ");
            // Create the new table
              String sql = "CREATE TABLE IF NOT EXISTS face (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `path` TEXT NOT NULL, `type` INTEGER NOT NULL, `faceId` TEXT NOT NULL, FOREIGN KEY(`faceId`) REFERENCES `user`(`faceId`) ON UPDATE CASCADE ON DELETE CASCADE )";
            database.execSQL(
                    sql);
            String sql2 = "CREATE INDEX IF NOT EXISTS `index_face_faceId` ON face (`faceId`)";
            database.execSQL(
                    sql2);
        }
    };
    
    private static RoomDemoDatabase buildDatabase(final Context appContext) {
        return Room.databaseBuilder(appContext, RoomDemoDatabase.class, DATABASE_NAME)
                .allowMainThreadQueries()
                 .addMigrations(MIGRATION_1_2)
                 //.openHelperFactory(new SafeHelperFactory("123456".toCharArray()))
                .addCallback(new Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);
    
                    }
    
                    @Override
                    public void onOpen(@NonNull SupportSQLiteDatabase db) {
                        super.onOpen(db);
                    }
    
                })
                .build();
    }
    
    • Sql 升级语句,可以根据Room导出的json文件获取

    多版本升级

    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)
    
    image

    四、 Room 关联表

    关联表配置
    • foreignKeys 配置外键
    • parentColumns:父表外键
    • childColumns:子表外键
    • NO_ACTION: parent表中某行被删掉(更新)后。child表中与parent这一行发生映射的行不发生任何改变
    • RESTRICT: parent表中想要删除(更新)某行。如果child表中有与这一行发生映射的行。那么改操作拒绝。
    • SET_NULL/SET_DEFAULT:parent表中某行被删掉(更新)后。child表中与parent这一行发生映射的行设置为NULL(DEFAULT)值。
    • CASCADE:parent表中某行被删掉(更新)后。child表中与parent这一行发生映射的行被删掉(其属性更新到对应设置)
        @Entity(tableName = FaceModel.FACE_TABLE_NAME,
            foreignKeys = {
                    @ForeignKey(entity = UserModel.class,
                            parentColumns = "faceId",
                            childColumns = "faceId",
                            onUpdate = ForeignKey.CASCADE,
                            onDelete = ForeignKey.CASCADE
                    )
            },
            indices = {@Index(value = {"faceId"})}
        )
    
    创建嵌套对象
        public class UserAndFaceModel {
        
            @Relation(parentColumn = "faceId", entityColumn = "faceId", entity = FaceModel.class)
            public List<FaceModel> faceModels;
        
            @Embedded
            public UserModel userModel;
        
        }
    
    • Relation: A convenience annotation which can be used in a POJO to automatically fetch relation entities.
      When the POJO is returned from a query, all of its relations are also fetched by Room.
    • Embedded: Marks a field of an Entity or POJO to allow nested fields (fields of the annotated
      field's class) to be referenced directly in the SQL queries.
    创建关联Dao
    @Dao
    public interface UserAndFaceDao {
    
        @Transaction // 保障事务
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.NAME + " = :name")
        LiveData<UserAndFaceModel> queryByName2Lv(String name);
    
        @Transaction
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.NAME + " = :name")
        UserAndFaceModel queryByName2Model(String name);
    
        @Transaction
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.FACE_ID + " = :faceId")
        LiveData<UserAndFaceModel> queryByFaceId2Lv(String faceId);
    
        @Transaction
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME + " WHERE "+
                UserModel.FACE_ID + " = :faceId")
        UserAndFaceModel queryByFaceId2Model(String faceId);
    
        @Transaction
        @Query("SELECT * FROM "+ UserModel.USER_TABLE_NAME )
        List<UserAndFaceModel> queryAll();
    
    }
    
    关联表数据插入注意
    • 保障事务
    RoomDemoDatabase.getInstance(MainActivity.this.getApplicationContext()).runInTransaction(new Runnable() {
                @Override
                public void run() {
                    UserModel userModel = new UserModel(1, "1", "2");
                    RoomDemoDatabase.getInstance(MainActivity.this.getApplicationContext()).userDao().insertUser(userModel);
                    FaceModel faceModel = new FaceModel("fa", 1, "fa");
                    RoomDemoDatabase.getInstance(MainActivity.this.getApplicationContext()).faceDao().insertFace(faceModel);
                }
            });
    

    五、 数据库数据加密

    5.1 文件加密

    SQLCipher
    SQLCipher是一个在SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能。
    
    • 加密性能高、开销小,只要5-15%的开销用于加密
    • 完全做到数据库100%加密
    • 采用良好的加密方式(CBC加密模式——密文分组链接模式)
    • 使用方便,做到应用级别加密
    • 采用OpenSSL加密库提供的算法
    • 开源,支持多平台
    • SQLCipjer加密原理介绍
    Realm
    Realm 介绍:
    • Realm 是一个 MVCC (多版本并发控制)数据库,
    • 由Y Combinator公司在2014年7月发布一款支持运行在手机、
    • 平板和可穿戴设备上的嵌入式数据库,目标是取代SQLite。
    • Realm 本质上是一个嵌入式数据库,他并不是基于SQLite所构建的。
    • 它拥有自己的数据库存储引擎,可以高效且快速地完成数据库的构建操作。
    • 和SQLite不同,它允许你在持久层直接和数据对象工作。
    • 在它之上是一个函数式风格的查询api,众多的努力让它比传统的SQLite 操作更快 。
    Realm 加密:
    • 借助 Realm,我们可以轻松地进行加密,
    • 因为我们可以轻松地决定数据库内核所应该做的事情。
    • 内部加密和通常在 Linux 当中做的加密哪样很类似。
    • 因为我们对整个文件建立了内存映射,
    • 因此我们可以对这部分内存进行保护。
    • 如果任何人打算读取这个加密的模块,
    • 我们就会抛出一个文件系统警告“有人正视图访问加密数据。
    • 只有解密此模块才能够让用户读取。”通过非常安全的技术我们有一个很高效的方式来实现加密。
    • 加密并不是在产品表面进行的一层封装,而是在内部就构建好的一项功能。

    5.2 内容加密

    • 在存储数据时加密内容,在查询时进行解密。但是这种方式不能彻底加密,数据库的表结构等信息还是能被查看到,另外检索数据也是一个问题。
    加密算法 描述 优点 缺点
    DES,3DES 对称加密算法 算法公开、计算量小、加密速度快、加密效率高 双方都使用同样密钥,安全性得不到保证
    AES 对称加密算法 算法公开、计算量小、加密速度快、加密效率高 双方都使用同样密钥,安全性得不到保证
    XOR 异或加密 两个变量的互换(不借助第三个变量),简单的数据加密 加密方式简单
    Base64 算不上什么加密算法,只是对数据进行编码传输
    SHA 非对称加密算法。安全散列算法,数字签名工具。著名的图片加载框架Glide在缓存key时就采用的此加密 破解难度高,不可逆 可以通过穷举法进行破解
    RSA 非对称加密算法,最流行的公钥密码算法,使用长度可变的秘钥 不可逆,既能用于数据加密,也可以应用于数字签名 RSA非对称加密内容长度有限制,1024位key的最多只能加密127位数据
    MD5 非对称加密算法。全程:Message-Digest Algorithm,翻译为消息摘要算法 不可逆,压缩性,不容易修改,容易计算 穷举法可以破解

    5.3 Room数据库数据库加密

    • SQLCipher并不直接支持Room的数据库进行加密,所以没法直接实现。
    • 可以通过开源库(swac-saferoom)进行数据加密(底层也是通过SQLCipher对数据库文件加密)
    集成 swac-saferoom
    添加 maven { url "https://s3.amazonaws.com/repo.commonsware.com" }
    dependencies {
      implementation 'com.commonsware.cwac:saferoom:1.1.3'
    }
    
    添加openHelperFactory
        private static AppDatabase buildDatabase(final Context appContext) {
            return Room.databaseBuilder(appContext, AppDatabase.class, DATABASE_NAME)
                .allowMainThreadQueries()
                .openHelperFactory(new SafeHelperFactory("123456".toCharArray()))
                .addCallback(new Callback() {
                    @Override
                    public void onCreate(@NonNull SupportSQLiteDatabase db) {
                        super.onCreate(db);
                    
                    }
    
                    @Override
                    public void onOpen(@NonNull SupportSQLiteDatabase db) {
                        super.onOpen(db);
                    }
                  
                })
                .build();
        }
    

    六、 Room与其他数据库对比

    参数 Room GreenDao Realm
    集成包大小 0.05M 0.05M 9.06M
    插入10000条速度 551ms 806ms 195ms
    查询10000条速度 126ms 71ms 4ms
    删除10000条速度 3ms 6ms 5ms
    更新10000条速度 622ms 838ms 242ms
    image

    七、 数据库调试工具分享

    debug调试

    使用debug-db 可以在浏览器查看表结构及数据

    普通数据库
        - implementation 'com.amitshekhar.android:debug-db:1.0.6'
    
    加密数据库
        debug {
            resValue("string", "PORT_NUMBER", "8081")
            resValue("string", "DB_PASSWORD_PERSON", "123456")
        }
        
        implementation 'com.amitshekhar.android:debug-db-encrypt:1.0.6'
    

    相关文章

      网友评论

        本文标题:Room使用详解及常用数据库对比

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