GreenDAO数据库升级问题的解决方法

GreenDAO数据库升级问题的解决方法

作者:陈小默
版权声明:禁止商用,转载请注明出处


关于GreenDAO在Android Studio的配置网上已经说的很多了,这里不再赘述。这篇文章主要讲一讲关于数据库版本升级的一些处理方式。我们边写边说:

以下代码为Kotlin语言编写,由于Kotlin也是一种基于JVM的语言,所以基本不影响Java开发者阅读

初始条件

目前信息:
version:1
db:my_db
table:user(id,name,age)

数据库管理器应该在Application或者其他地方以单例形式创建

private var master: DaoMaster? = null

object DBMaster {
    fun init(context: Context, dbName: String) {
        if (master == null) {
            synchronized(DBMaster, {
                ->
                if (master == null) {
                    var help = object : DaoMaster.DevHelper(context, dbName, null)
                    var db = help.writableDb
                    master = DaoMaster(db)
                }
            })
        }
    }

    fun getMaster() = if (master == null) throw RuntimeException("not init") else master

    fun newSession() = getMaster()?.newSession()
}

Generator类信息

class MyGenerator

fun main(args: Array<String>) {
    val schema = Schema(1, "com.cxm.bean")

    //这里创建了一个一张数据表
    val note = schema.addEntity("User")
    note.addIdProperty()
    note.addStringProperty("name").notNull()
    note.addIntProperty("age").notNull()

    //以下操作寻找存放bean的目录
    var path = MyGenerator::class.java.getResource(".").getPath()
    path = path.substring(0, path.lastIndexOf("mydaogenerator/build/classes/main/com/xcm/"))
    path += "app/src/main/java-gen"

    //将创建好的Bean以及Dao文件存放到相应的目录
    DaoGenerator().generateAll(schema, path)
}

以上基本信息就写好了,现在我们写一个MainActivity测试一下:

class MainActivity : AppCompatActivity() {
    private var daoSession: DaoSession? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_green)
        daoSession = DBMaster.newSession()

        var user = User(null, "CXM", 22)
        getUserDao()?.insert(user)

        val queryList = getUserDao()?.queryBuilder()?.build()?.list()
        queryList?.forEach {
            user
            ->
            Log.e("--data--", user.toString())
        }
    }

    private fun getUserDao() = daoSession?.userDao
}

这时候每运行一次程序就会在控制台输出多输出一条log信息

1,直接升级版本

假如我们不进行任何处理直接在上述程序中将MyGenerator.main方法中的版本信息改为2

val schema = Schema(2, "com.cxm.bean")
目前信息:
version:2
db:my_db
table:user(id,name,age)

重新生成Dao和Bean对象再次运行程序,我们发现原来的Log信息没有了,也就是说表应该是被重建了,至于到底发生了什么,我们看一下GreenDAO的源代码:

    /** WARNING: Drops all table on Upgrade! Use only during development. */
    public static class DevOpenHelper extends OpenHelper {
        ...
    }

我们直接看注释:

警告:(此类)会在数据库升级过程中销毁所有的表!仅限在开发过程中使用

这个注释已经说的很清楚了,这个Helper类是专门提供给开发环境下测试的,并不适用于实际应用环境,实际应用中肯定要自定义类或者重写这些方法。然后我们看这个仅供开发环境使用的Helper是如何处理升级的:

        @Override
        public void onUpgrade(Database db, int oldVersion, int newVersion) {
            Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
            dropAllTables(db, true);
            onCreate(db);
        }

注:这里的onCreate(db)是其父类中的方法:

    /**
     * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
     */
    public static abstract class OpenHelper extends DatabaseOpenHelper {
        ...
        @Override
        public void onCreate(Database db) {
            Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
            createAllTables(db, false);
        }
    }

注:这里的createAllTables(db, false)是DaoMaster中的一个静态方法:

    /** Creates underlying database table using DAOs. */
    public static void createAllTables(Database db, boolean ifNotExists) {
        UserDao.createTable(db, ifNotExists);
    }

    /** Drops underlying database table using DAOs. */
    public static void dropAllTables(Database db, boolean ifExists) {
        UserDao.dropTable(db, ifExists);
    }

其实源码看到这里就差不多了,再深层的代码我们一会再看。到这里我们应该知道为什么我们要先去创建一个Java模块专门去生成这些Dao、Master和Bean了。Master中会统一创建好一系列的创建和销毁数据库的全部代码。而这些代码会在数据库同一个版本第一次被创建时执行。如果我们要想在升级时管理我们的数据库就必须了解GreenDAO的升级过程。现在我们自己定义一个类去继承OpenHelper并重写其中的onUpgrage方法。

2,重写onUpgrade方法

private var master: DaoMaster? = null

private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
    //这里用来操作数据库升级过程
}

object DBMaster {
    fun init(context: Context, dbName: String) {
        if (master == null) {
            synchronized(DBMaster, {
                ->
                if (master == null) {
                    var help = object : DaoMaster.OpenHelper(context, dbName, null) {
                        override fun onUpgrade(db: Database, oldVersion: Int, newVersion: Int) {
                            upgrade(db, oldVersion, newVersion)
                        }
                    }
                    var db = help.writableDb
                    master = DaoMaster(db)
                }
            })
        }
    }

    fun getMaster() = if (master == null) throw RuntimeException("not init") else master

    fun newSession() = getMaster()?.newSession()
}
目前信息:
version:3
db:my_db
table:user(id,name,age)

由于我们在onUpgrade方法中什么都没有做,这时我们将数据库版本升级为3后运行原来的数据确实没有被销毁。这是因为我们没有加入dropAllTables方法,但是我们也没有加入createAllTables方法。这时就会产生一个新的问题:

3,在2号实验的基础上增加表

目前信息:
version:4
db:my_db
table:user(id,name,age),course(id,name,roomNumber)

现在我们添加一个课程信息,存放课程名称和上课教室编号。这些操作要在MyGenerator.main方法中添加如下代码

    //创建表Course
    val course = schema.addEntity("Course")
    course.addIdProperty()
    course.addStringProperty("name").notNull()
    course.addIntProperty("roomNumber").notNull()

现在重新生成代码,并在MainActivity中添加数据

    var course = Course(null, "语文", 1)
    getCourseDao()?.insert(course)

如果你运行了这段代码是一定会报错的,因为我们并没有创建这个表。对于这个错误我们的结局方案是:

4,在upgrade方法中添加createAllTable方法

private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
    DaoMaster.createAllTables(db, true)
}
目前信息:
version:5
db:my_db
table:user(id,name,age),course(id,name,roomNumber)

此时我们就能正常的运行程序了。但是这并不能解决任何问题,因为假如我们修改了User表的结构,比如增加了一个表示性别sex:Boolean的字段

5,在上述实验下给User增加字段

此处省略生成和运行过程。。。
目前信息:
version:6
db:my_db
table:user(id,name,age,sex),course(id,name,roomNumber)
由于没有sex字段,想都不用想,一定会报错。

6,使用Sql语句增加字段

这里继续修改upgrade方法

private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
    DaoMaster.createAllTables(db, true)
    db.execSQL("""ALTER TABLE User ADD "SEX" INTEGER""")
}

这里我们可能遇见的最大的问题是不知道Java/Kotlin中的类型和Sqlite中的类型如何对应,这里我教大家一个不用谷歌的方法,就是看源码。比如我要在User中增加一个Kotlin中为Boolean的类型,我们可以打开自动生成的UserDao.java中找createTable方法,这里我找到的就是:

    /** Creates the underlying database table. */
    public static void createTable(Database db, boolean ifNotExists) {
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "\"USER\" (" + //
                "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
                "\"NAME\" TEXT NOT NULL ," + // 1: name
                "\"AGE\" INTEGER NOT NULL ," + // 2: birth
                "\"SEX\" INTEGER);"); // 3: sex
    }

我们看到这里name的Sql类型是text,age和sex对应的类型都是integer。
所以再次升级数据库就可以正常升级了,根据此方法,无论增加表,删除表,增加字段还是删除字段都能成。

温馨提示:

由于在数据库升级过程中并不会依次递增的执行onUpdrade,比如从1版本升级到10,其中并不先执行从1-2,再从2-3 …而是直接从1-10。这在实际应用上的表现就是,用户下载了版本1,开发者在版本2给数据库增加了一个字段,然后在版本3中开发者想既然已经升级过数据库了,就不需要再保留这句sql语句了,于是就删除了这句话。可是用户并没有升级版本2,而是在版本3上线后才进行更新。这样造成的后果就是由于缺少字段,用户应用不断崩溃最后使用户不信任产品直至卸载。所以开发者在考虑是否删除某些语句时一定要事先询问产品运维组各个版本的用户量进行综合考量。比如采取这样的形式:

private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
    for (i in oldVersion..newVersion) {
        when (i) {
            1 -> {
                //1-2
            }
            2 -> {
                //2-3
            }
            3 -> {
                //3-4
            }
            else -> {
                //other
            }
        }
    }
}

仅供参考

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值