Room Library也是作为Jetpack的一个组件使用,提供了一个覆盖SQLite的抽象层,采用注解处理工具辅助生成对应的数据库相关的代码,可以更加快速的进行开发和测试,对于数据库存储google也建议使用Room Library代替直接使用SQLite;
官方文档:https://developer.android.google.cn/training/data-storage/room
下面简单引入Room Library:
一,依赖:
plugin:
apply plugin: 'kotlin-kapt'
dependency:
implementation 'android.arch.persistence.room:runtime:1.0.0' kapt 'android.arch.persistence.room:compiler:1.0.0'
二,建表:
通过@Entity设置PersonlInfo为数据库的一张数据表,并设置基本的字段,如果设置mName为peimaryKey,需要指定mName字段为非空约束;
@Entity(tableName = "user_info") public class PersonalInfo { @ColumnInfo(name = "name") @PrimaryKey @NonNull public String mName; @ColumnInfo(name = "age") public String mAge; //@ColumnInfo(name = "interest") //public String mInterest; }
在简单定义一张contacts联系人表;
@Entity(tableName = "contacts") public class Contact { @PrimaryKey @ColumnInfo(name = "contactId") public int mContactId; @ColumnInfo(name = "name") public String mName; }
在chatMesage表中设置aboutContactMessage和contact表的contactId关联,并且设置当删除contact数据时,附带的chatMessage中指定的aboutContactMessage等于contactId的所有数据也被删除;
@Entity( tableName = "chatMessage", foreignKeys = { @ForeignKey( entity = Contact.class,//外键关联 parentColumns = "contactId", childColumns = "aboutContactMessage", onDelete = CASCADE, onUpdate = NO_ACTION, deferred = false ) } ) public class ChatMessage { @PrimaryKey(autoGenerate = true) public int mid; @ColumnInfo(name = "fromOthers") public int mFromOthers; @ColumnInfo(name = "aboutContactMessage") public int mAboutContactMessage; @ColumnInfo(name = "startTime")public long mShowTime; @ColumnInfo(name = "stringContent") public String mStringContent; }
三,数据库访问DAO:
@Dao public interface ChatMessageDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void insertChatMessage(ChatMessage message); @Delete void deleteData(ChatMessage message); @Update void upDateData(ChatMessage message); @Query("SELECT * FROM chatMessage WHERE aboutContactMessage LIKE :aboutContactMessage") Flowable<List<ChatMessage>> rxQueryChatMessageWithContact(int aboutContactMessage); }
插入和更新操作数据的时候,当主键出现冲突时,我们也可以设置不同的策略, 比如IGNORE或者REPLACE等,同时Room数据也支持rxJava操作,但是需要在dependencies中添加依赖:implementation "android.arch.persistence.room:rxjava2:1.0.0"
四,数据库连接DataBase:
@Database(entities = { PersonalInfo.class, Contact.class, ChatMessage.class // Account.class }, version = 1, exportSchema = true) public abstract class ProjectDataBase extends RoomDatabase { private static final String DB_NAME = "DataBase.db"; private static ProjectDataBase instance; public static synchronized ProjectDataBase getInstance(Context context) { if (instance == null) { instance = create(context); } return instance; } private static ProjectDataBase create(final Context context) { return Room.databaseBuilder( context, ProjectDataBase.class, DB_NAME) .addCallback(new Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { super.onCreate(db); //创建各个数据表时,并且创建完毕调用,后续并不会调用 Log.d("dataBase", "db onCreate"); } @Override public void onOpen(@NonNull SupportSQLiteDatabase db) { super.onOpen(db); //打开数据库连接时调用 Log.d("dataBase", "db onOpen"); } }) // .addMigrations(new MyMigration2_3(1, 2)) .allowMainThreadQueries()//Cannot access database on the main thread // .fallbackToDestructiveMigration()//(当不指定Migration时,可以跟新表,但是用户原始数据被清除) .build(); } private static class MyMigration1_2 extends Migration { public MyMigration1_2(int startVersion, int endVersion) { super(startVersion, endVersion); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { Log.d("dataBase", "db migrate1"); database.execSQL("ALTER TABLE user_info ADD COLUMN interest TEXT"); } } private static class MyMigration2_3 extends Migration { public MyMigration2_3(int startVersion, int endVersion) { super(startVersion, endVersion); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { Log.d("dataBase", "db migrate2"); database.execSQL(" CREATE TABLE [account](\n" + " [pId] INTEGER NOT NULL PRIMARY KEY, \n" + " [count] TEXT REFERENCES [user_info]([name]));"); } } public abstract ChatMessageDao getChatMessageDao(); public abstract ContactDao getContactDao(); public abstract PersonalInfoDao getPersonalInfoDao(); }
RoomDatabase包含数据库持有者,并作为应用程序持久关系数据的基础连接的主要访问点,我们定义的抽象方法,通过kapt自动生成实现代码,可以通过点击ChatMessageData的实现方法,在buid/generated/kapt下找到这些实现类
五,增删改查简单操作:
简单测试一下基于Room数据库的操作:
插入个人基本信息:
private fun justInsetPersonal() { ProjectDataBase.getInstance(this).personalInfoDao.insertPerson(PersonalInfo().apply { this.mName = "zhangSan" this.mAge = "18" }) }
插入联系人信息:
private fun justInsetContact() { ProjectDataBase.getInstance(this).contactDao.apply { for (i in 1..10) { insertPerson(Contact().apply { this.mContactId = i this.mName = "name$i" }) } } }
插入一些chat message:
private fun justInsetMessage() { ProjectDataBase.getInstance(this).apply { for (i in 1..10) { chatMessageDao.insertChatMessage(ChatMessage().apply { this.mFromOthers = 0 this.mAboutContactMessage = i this.mStartTime = System.currentTimeMillis() this.mStringContent = "hello zhangSan" }) } chatMessageDao.insertChatMessage(ChatMessage().apply { this.mFromOthers = 1 this.mAboutContactMessage = 3 this.mStartTime = System.currentTimeMillis() this.mStringContent = "你好 name3" }) } }
查询简单操作:
base.chatMessageDao.rxQueryChatMessageWithContact(3).subscribe { it.forEach { Log.d("dataBase", "rxQueryChatMessageWithContact: ${it.toString()}") } }
我遇到的问题是,db文件导出到桌面没有数据,我们生成的数据库文件包含db-shm和db-wal后缀名的两个零时文件,9.0引入,在stackoverflow上找到了原因,这里有一些介绍https://www.sqlite.org/tempfiles.html,处理方式主要有两种,设置disableWriteAheadLogging(),或者写入数据后便关闭数据连接close(),并刷新我们的databases文件夹,就会自动删除临时文件,导入到桌面使用Expert或者DB Browser都可以查看,也可安装studio插件;
六,数据库版本升级:
数据库升级,我们将 version = 1版本增加,并设置Room.databaseBuilder().addMigrations(1,2),既可以完成数据的迁移;
比如在user_info表中增加一个interest字段:
private static class MyMigration1_2 extends Migration { public MyMigration1_2(int startVersion, int endVersion) { super(startVersion, endVersion); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { Log.d("dataBase", "db migrate1"); database.execSQL("ALTER TABLE user_info ADD COLUMN interest TEXT"); } }
Room.databaseBuilder().addMigrations()设置数据库迁移,这里以在user_info表中增加一个interest字段为例,先在PersonalInfo类中增加这个字段, @columnInfo(name = "interest"),然后执行上面execSQL语句,将字段添加到user_info数据表中;
下面这个升级是增加一张关联user_info表的account表:
@Entity( tableName = "account", foreignKeys = { @ForeignKey( entity = PersonalInfo.class, parentColumns = "name", childColumns = "count" ) } ) public class Account { @PrimaryKey public int pId; @ColumnInfo(name = "count") public String mCount; }
private static class MyMigration2_3 extends Migration { public MyMigration2_3(int startVersion, int endVersion) { super(startVersion, endVersion); } @Override public void migrate(@NonNull SupportSQLiteDatabase database) { Log.d("dataBase", "db migrate2"); database.execSQL(" CREATE TABLE [account](\n" + " [pId] INTEGER NOT NULL PRIMARY KEY, \n" + " [count] TEXT REFERENCES [user_info]([name]));"); } }
先增加一个@Entity类Account,设置表名为account,对于execSQL语句,我是直接在Expert中生成一个account表查看SQL语句进行测试,我们设置的entity类所生成的表和execSQL语句生成的表的构建信息要匹配,不然会出现不匹配异常;
此外,当设置fallbackToDestructiveMigration()时,也可以完成数据库的升级,但是会将之前数据库中已有的数据清除;
同时可以在defaultConfig配置块中设置:
javaCompileOptions { annotationProcessorOptions { arguments = ["room.schemaLocation": "$projectDir/schemas".toString()] } }
执行ReBuild会将当前版本的数据库信息生成对应的1.json文件,保留升级之前的数据库信息;