嵌入式Sqlite数据库(三):实战注解ORM数据库框架

2021-01-15  本文已影响0人  bug喵喵

前言

AndroidORM框架有很多,比如RealmgreenDAOLitePalDBFlowafinalSugarORMORMLiteLiteORM,还有AnkoManagedSqliteOpenHelper。其中RealmGreenDAO在2017年百大框架排行榜里面排名最高,27名和28名。下面就来简单的说说这些框架并且说说Realm的使用和封装。

ORM框架

如果使用Android SQLite创建一个数据库需要实现下面的步骤:

原生操作复杂,写SQL语句容易出错。各种ORM的出现,使它的变得操作更加简单。

Realm

Github路径Realm-Java,下面进一步讲解具体使用

GreenDAO

使用教程和源码参考greenDAO-Github

LitePal

DBFlow

使用文档参考DBFlowDBFlow-Github

afinal

使用文档参考afinalafinal-Github

SugarORM

使用文档参考SugarORM-Github

ORMLite

使用文档参考鸿洋大神介绍介绍ORMLite博客ORMLite-Android-Github

LiteORM

Anko-SQLite
通过kotlin+anko简化了创建原生Android数据库表操作,详情使用文档参考Anko-SQLite

GithubStar来说,则RealmGreen占优势,同时这两个的功能十分强大。
ORM库大小来说,则GreenDaoLitePalLiteORM等轻量级的占优势。
ORM的使用配置简单程度来说,则LitePalafinalLiteORM占优势。
综上所述,从稳定性,安全性,功能的强大性选RealmGreenDaoORMLite似乎更好,从轻量程度性,配置简单化来说选LitePalafinalLiteORMSugarORMDBFlow似乎更好。当然,如果想不依赖框架,使用Anko-SQLite来实现就再好不过了。

Realm基础

集成

project里面的build.gradle加入

classpath "io.realm:realm-gradle-plugin:4.1.1"

然后在appbuild.gradle加入

apply plugin: 'realm-android'

同时在defaultConfig里面加入

ndk{ abiFilters "armeabi"}

可减小Realm库的大小

数据库表

下面简单定义一个User

public class User extends RealmObject {
    @PrimaryKey
    private int id;
    private String name;
    private int age;
    @Ignore
    private int sessionId;
    public boolean IsEmptyName(){
        return name.isEmpty();
    }
//----------下面是Set和Get方法,此处省略-----------
}

RealmObject是一个抽象类,如果想使用接口形式,使用RealmModel和注解@RealmClass也是同样的效果。属性添加@PrimaryKey注解即表示表的主键,使用@Ignore即表示该属性不添加到库里面,同时也可以在User表里面添加PublicProtected方法。如上面的IsEmptyName方法,所以几乎可以把User表当PoJo来使用

初始化Realm

Application里面的onCreate方法里面执行

 Realm.init(this)

增删修改操作
结合上篇MVP的封装以及上面User表,实现下图效果。

img

增删修改效果图.png

mvpView.mRealm.beginTransaction()
mvpView.mRealm.copyToRealmOrUpdate(createUser())
mvpView.mRealm.commitTransaction()

或者

mvpView.mRealm.executeTransaction {
       realm ->
      realm.copyToRealmOrUpdate(createUser())
}

其中mvpView.mRealm是在BaseActivity/BaseFragment实例化的一个Realm

val mRealm = Realm.getDefaultInstance()
mvpView.mRealm.executeTransactionAsync({
            it.copyToRealmOrUpdate(createUser())
        },{},{}).bindTo(mvpView.realmAsyncList)

executeTransactionAsync分别对应executeOnSuccessOnError,其中OnSuccessOnError也可不回调。

   val results = mvpView.mRealm.where(User::class.java).equalTo("name","Android").findAllAsync()
    mvpView.UpdateUI(results)

这种写法类似JavaFuture,查询将会在后台线程中被执行,当其完成时,之前返回的 RealmResults 实例会被更新。

 val results = findAllUser()
 mvpView.mRealm.executeTransaction {
            _ ->
            results.deleteFirstFromRealm()
}
results.deleteAllFromRealm()

最后的Presenter就如下

class RealmPresenter:BasePresenter<RealmFragment>() {
    private val names = arrayOf("Android","Java","Kotlin","JS","PHP")
    private val idCount:AtomicInteger = AtomicInteger(0)
    //同步增加或者修改
    fun syncAddOrUpdateItem(){
      //第一种方式,自己手动管理事务
        mvpView.mRealm.beginTransaction()
        mvpView.mRealm.copyToRealmOrUpdate(createUser())
        mvpView.mRealm.commitTransaction()
        mvpView.UpdateUI(findAllUser())
      /*
      //第二种方式,Realm自动管理事务  
        mvpView.mRealm.executeTransaction {
            realm ->
            realm.copyToRealmOrUpdate(createUser())
            mvpView.UpdateUI(findAllUser())
        }*/
    }
   //异步增加或者修改
    fun asyncAddOrUpdateItem(){
        mvpView.mRealm.executeTransactionAsync({
            it.copyToRealmOrUpdate(createUser())
        },{
            mvpView.UpdateUI(findAllUser())
        },{
        }).bindTo(mvpView.realmAsyncList)
    }
    //异步查询
    fun asyncQueryItem(){
        val results = mvpView.mRealm.where(User::class.java).equalTo("name","Android").findAllAsync()
        mvpView.UpdateUI(results)
    }
  //删除
    fun removeItem(){
        val results = findAllUser()
        mvpView.mRealm.executeTransaction {
            _ ->
            results.deleteFirstFromRealm()
            if(idCount.get()>1) idCount.decrementAndGet()
            mvpView.UpdateUI(results)
        }
    }
  //清除
    fun removeAll(){
        val results = findAllUser()
        mvpView.mRealm.executeTransaction {
            _ ->
            results.deleteAllFromRealm()
            idCount.set(0)
            mvpView.UpdateUI(results)
        }
    }
    private fun createUser():User{
        val user = User()
        user.id = idCount.get()
        user.name = names[(Math.random()*4).toInt()]
        user.age = (Math.random()*10).toInt()
        idCount.incrementAndGet()
        return user
    }
    //同步查询
    private fun findAllUser():RealmResults<User>{
        return mvpView.mRealm.where(User::class.java).findAll()
    }
}

JSON

Realm是支持json数据的,可以通过StringInputStreamJsonObject直接传入保存到对应的表里面

三种方式如下:

img

json方式.png

举个例子,存储一个全国城市列表的jsonCity表里面

首先准备一个city.json文件放在raw目录下面,json格式如下

[
  {
    "area": "010",
    "code": "110000",
    "level": "1",
    "name": "北京市",
    "prefix": "市"
  },
  {
    "area": "010",
    "code": "110101",
    "level": "2",
    "name": "东城区",
    "prefix": "区"
  },
  {
    "area": "010",
    "code": "110102",
    "level": "2",
    "name": "西城区",
    "prefix": "区"
  },
//省略.....
]

然后我们定义一个City

public class City extends RealmObject {
    @PrimaryKey
    private String code;
    private String area;
    private String level;
    private String name;
    private String prefix;
  //省略Get和Set方法
}

这里封装一个创建Realm对象的帮助类,支持多Realm

首先抽象出变化需要配置的RealmConfiguration的参数,定义一个IRealmMigrate接口,

interface IRealmMigrate {
    fun src():InputStream?
    fun realmName():String
    fun schemaVersion():Int
    fun migration(): RealmMigration
}

其中
src()指的是需要本地资源迁移的时候传入的InputStream
realmName()指的是realm自定义的realm后缀的文件,默认存储在data/data/<packagename>/file/路径下,
schemaVersion()默认的数据库版本,
migration() 实现RealmMigration对升级数据库的一些操作

实现一个RealmHelper帮助类

object RealmHelper {
    private val mMigrationMap:SparseArrayCompat<Realm> = SparseArrayCompat()
    fun getRealmInstance(migration:IRealmMigrate):Realm{
        val key = migration.realmName().hashCode()
        var mRealm:Realm? = mMigrationMap.get(key)
        if(mRealm==null||mRealm.isClosed||mMigrationMap.indexOfKey(key)<0){
            val migrationConfig = RealmConfiguration.Builder()
                    .name(migration.realmName())
                    .schemaVersion(migration.schemaVersion().toLong())
                    .migration(migration.migration())
                    .build()
            if(migration.src()!=null){
                val file = File(migrationConfig!!.path)
                if (!file.exists()||file.length() == 0L) {
                    file.delete()
                    file.createNewFile()
                    file.outputStream().use { out -> migration.src()!!.use { it.copyTo(out) } }
                }
            }
            mRealm = Realm.getInstance(migrationConfig)

            mMigrationMap.put(key,mRealm)
        }
       return mRealm!!

    }
    fun clear(){
        mMigrationMap.clear()
    }
}

外部传入IRealmMigrate,然后对RealmConfiguration进行设置,同时通过SparseArrayCompat进行保存,另外当退出app的时候调用clear()方法。

最后在application里面调用initCity()方法

  private fun initCity():App{
        val inStream = this.resources.openRawResource(R.raw.city)
        val mRealm = RealmHelper.getRealmInstance(AppRealmMigrateImpl())
        mRealm.use { it ->
            it.executeTransaction {
                realm ->
                realm.createOrUpdateAllFromJson(City::class.java,inStream)
            }
        }
        return this
    }

这样通过调用realm.createAllFromJson(xx)对应的City表就会有city.json里面的数据了。在使用的时候通过查表就可以查到对应的城市数据。City表查询结果

img

查询结果.png

注意创建一个Realm,对应就要close一次。所以上文的BaseActivity/BaseFragmentRealm可以改成

val mRealm = RealmHelper.getRealmInstance(AppRealmMigrateImpl())

然后在OnDestroy()里面进行close就行了。

迁移/升级

当数据库表发生变化的时候,如果不进行处理,Realm会抛出类似下面这样的错误

io.realm.exceptions.RealmMigrationNeededException: Field count is less than expected - expected 4 but was 3

所以要对数据进行迁移,也就是数据库升级
这里对上面的User表增加一个字段

public class User extends RealmObject {
//同上...
 @Required
private Integer sex;
//省略Get和Set方法
}

其中@Required是指sex不能为null,然后定义一个方法实现RealmMigration

@Suppress("INACCESSIBLE_TYPE")
class AppMigration: RealmMigration {
    override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
        val schema = realm.schema as RealmSchema
        if(oldVersion==0L&&newVersion==1L){
            val userSchema = schema.get(User::class.java.simpleName)
            if(userSchema!=null&&!userSchema.hasField("sex")){
                userSchema.addField("sex",Int::class.java,FieldAttribute.REQUIRED)
            }
            oldVersion.plus(1)
        }
    }
}

同时对User表增加数据的时候,需要设置sex的值。这样就ok了。

不过数据迁移的时候只能读取Realm后缀的文件,例如db文件貌似不支持。

Configurations cannot be different if used to open the same file. The most likely cause is that equals() and hashCode() are not overridden in the migration class: com.data.lib.impl.AppMigration

RealmConfiguration相同的情況下,Realm.getInstance(migrationConfig)不能获取两次

@Required annotation is unnecessary for primitive field "xxx".

只有Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[], Date 这些数据类型才支持@Required

总结

关于Realm的加密功能,异步线程监听功能,集合通知的一些注意事项,结合Gson使用等等之类的功能都可以参考Realm文档

Realm是一批好马,操作查询之类的确实是相当的快, 但是需要驯服得熟读Realm文档。

上一篇下一篇

猜你喜欢

热点阅读