[关闭]
@cxm-2016 2016-08-05T14:17:38.000000Z 字数 5988 阅读 4565

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

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


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

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

初始条件

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

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

  1. private var master: DaoMaster? = null
  2. object DBMaster {
  3. fun init(context: Context, dbName: String) {
  4. if (master == null) {
  5. synchronized(DBMaster, {
  6. ->
  7. if (master == null) {
  8. var help = object : DaoMaster.DevHelper(context, dbName, null)
  9. var db = help.writableDb
  10. master = DaoMaster(db)
  11. }
  12. })
  13. }
  14. }
  15. fun getMaster() = if (master == null) throw RuntimeException("not init") else master
  16. fun newSession() = getMaster()?.newSession()
  17. }

Generator类信息

  1. class MyGenerator
  2. fun main(args: Array<String>) {
  3. val schema = Schema(1, "com.cxm.bean")
  4. //这里创建了一个一张数据表
  5. val note = schema.addEntity("User")
  6. note.addIdProperty()
  7. note.addStringProperty("name").notNull()
  8. note.addIntProperty("age").notNull()
  9. //以下操作寻找存放bean的目录
  10. var path = MyGenerator::class.java.getResource(".").getPath()
  11. path = path.substring(0, path.lastIndexOf("mydaogenerator/build/classes/main/com/xcm/"))
  12. path += "app/src/main/java-gen"
  13. //将创建好的Bean以及Dao文件存放到相应的目录
  14. DaoGenerator().generateAll(schema, path)
  15. }

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

  1. class MainActivity : AppCompatActivity() {
  2. private var daoSession: DaoSession? = null
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.activity_green)
  6. daoSession = DBMaster.newSession()
  7. var user = User(null, "CXM", 22)
  8. getUserDao()?.insert(user)
  9. val queryList = getUserDao()?.queryBuilder()?.build()?.list()
  10. queryList?.forEach {
  11. user
  12. ->
  13. Log.e("--data--", user.toString())
  14. }
  15. }
  16. private fun getUserDao() = daoSession?.userDao
  17. }

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

1,直接升级版本

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

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

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

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

我们直接看注释:

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

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

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

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

  1. /**
  2. * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
  3. */
  4. public static abstract class OpenHelper extends DatabaseOpenHelper {
  5. ...
  6. @Override
  7. public void onCreate(Database db) {
  8. Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
  9. createAllTables(db, false);
  10. }
  11. }

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

  1. /** Creates underlying database table using DAOs. */
  2. public static void createAllTables(Database db, boolean ifNotExists) {
  3. UserDao.createTable(db, ifNotExists);
  4. }
  5. /** Drops underlying database table using DAOs. */
  6. public static void dropAllTables(Database db, boolean ifExists) {
  7. UserDao.dropTable(db, ifExists);
  8. }

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

2,重写onUpgrade方法

  1. private var master: DaoMaster? = null
  2. private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
  3. //这里用来操作数据库升级过程
  4. }
  5. object DBMaster {
  6. fun init(context: Context, dbName: String) {
  7. if (master == null) {
  8. synchronized(DBMaster, {
  9. ->
  10. if (master == null) {
  11. var help = object : DaoMaster.OpenHelper(context, dbName, null) {
  12. override fun onUpgrade(db: Database, oldVersion: Int, newVersion: Int) {
  13. upgrade(db, oldVersion, newVersion)
  14. }
  15. }
  16. var db = help.writableDb
  17. master = DaoMaster(db)
  18. }
  19. })
  20. }
  21. }
  22. fun getMaster() = if (master == null) throw RuntimeException("not init") else master
  23. fun newSession() = getMaster()?.newSession()
  24. }
目前信息:
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方法中添加如下代码

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

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

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

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

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

  1. private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
  2. DaoMaster.createAllTables(db, true)
  3. }
目前信息:
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方法

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

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

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

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

温馨提示:

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

  1. private fun upgrade(db: Database, oldVersion: Int, newVersion: Int) {
  2. for (i in oldVersion..newVersion) {
  3. when (i) {
  4. 1 -> {
  5. //1-2
  6. }
  7. 2 -> {
  8. //2-3
  9. }
  10. 3 -> {
  11. //3-4
  12. }
  13. else -> {
  14. //other
  15. }
  16. }
  17. }
  18. }

仅供参考

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注