GreenDAO数据库升级,GreenDao数据库版本升级之手把手教你如何做到不删库,不拷贝表的升级方式深入代码浅析GreenDao升级

  • 前言
  • 数据库升级需要哪些东西??
  • GreenDao中数据库升级函数
  • 从数据库怎么的用法中讲如何修改升级
  • 如何避免删库
  • 删库问题解决了,那么我怎么升级啊???
  • 然后到了一个很关键的东西,修改实体类
  • 修改表结构信息
  • 添加表
  • 升级步骤整理


从代码角度浅析GreenDao的升级

提示


前言

最近在公司做项目的时候,因为要做数据缓存,所以就引入了现在极其非常流行的框架GreenDao,使用下来确实感觉方便快捷,懒人必备,只要写个实体类,基本上其他的都是用插件工具直接生成,简直不要太方便。但是前两天,因为同事在未进行数据库升级的基础上,直接对数据库结构,或者表结构信息等进行的修改,导致出现了一些异常崩溃情况。因为我之前做过的项目有一部分数据存储都是直接原生接口一路写下来的,所以也就想着怎么解决由于数据库版本管理不当导致的数据库升级异常。
也在网上搜了一些数据库升级建议,方案,也在源码里面review了部分升级的代码。结合源码这些,再结合GreenDao实际操作的步骤,场景,做了点简单的总结

GreenDao自带默认升级弊端:

1.GreenDao对数据库升级函数做了重写,也就是默认情况下是会做删库,删表操作,导致数据丢失

现行的方案
2:在网上其实很多方案都是先将数据表中的数据进行一个备份,然后删表,建表,再做数据恢复动作。其实这种方案当然是有效的啊,but 在数据库版本控制过程中,其实对删库,删表动作有着极强的敏感性,所以有一个梗叫删库跑路,所以其实在升级过程中,如果实在没有办法进行规避的情况下,才做吧

源码走一波先

数据库升级需要哪些东西??

数据库升级,顾明思议,其实也就是对数据库本身表进行增删改,对数据库表结构进行增删改改变化,由于本身android的sqlite数据库中有很强的前后版本关系,在前后升级的时候都是会有相应触发回调下来。但是你第一步要知道的就是所谓的升级,你需要哪些先决必要条件

1:版本号控制,前后版本号肯定不能一致,也不能有降级操作
2:需要修改的是什么??决定你要执行什么样的sql
3:数据完整性上面要得到保证,我不能升级个数据库,完了就把我所有的数据都清掉了,那还不如不去升级

下面之间low代码,还是从代码里面去看升级方式吧

GreenDao中数据库升级函数

首先数据库创建和管理的类都是的DaoMaster.DevOpenHelper,获取对应的Helper对象,不做过多应用,直接看源码

/** WARNING: Drops all table on Upgrade! Use only during development. */
    public static class DevOpenHelper extends OpenHelper {
        public DevOpenHelper(Context context, String name) {
            super(context, name);
        }

        public DevOpenHelper(Context context, String name, CursorFactory factory) {
            super(context, name, factory);
        }

        @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);
        }
    }

源码中这个DebOpenHelper对象其实是一个静态的内部类,继承自OpenHelper,看到这几个函数,其实想到的应该SqliteOpenHelper里面也似曾相识啊,有一个doUpgrade方法

@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);
        }

这里面就看到了,原来GreenDao数据库在升级的时候,是先dropAllTables(db, true);删除了所有数据库,然后又走了onCreate方法,难怪升级会删库重建表。

再继续往下追一下,它的父类OpenHelper

/**
     * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
     */
    public static abstract class OpenHelper extends DatabaseOpenHelper {
        public OpenHelper(Context context, String name) {
            super(context, name, SCHEMA_VERSION);
        }

        public OpenHelper(Context context, String name, CursorFactory factory) {
            super(context, name, factory, SCHEMA_VERSION);
        }

        @Override
        public void onCreate(Database db) {
            Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
            createAllTables(db, false);
        }
    }

可以看到,父类又是一个叫DatabaseOpenHelper的类,但是构造函数其实都是继承于父类的实现形式,然后再onCreate中进行创建表的操作,但是很奇怪OpenHelper这个类使一个静态抽象内部类,但是没见到有抽象方法啊,完了我就又进去追了一把,找到DatabaseOpenHelper这个类。

/**
 * SQLiteOpenHelper to allow working with greenDAO's {@link Database} abstraction to create and update database schemas.
 */
public abstract class DatabaseOpenHelper extends SQLiteOpenHelper {

    private final Context context;
    private final String name;
    private final int version;

    private EncryptedHelper encryptedHelper;
    private boolean loadSQLCipherNativeLibs = true;

    public DatabaseOpenHelper(Context context, String name, int version) {
        this(context, name, null, version);
    }

    public DatabaseOpenHelper(Context context, String name, CursorFactory factory, int version) {
        super(context, name, factory, version);
        this.context = context;
        this.name = name;
        this.version = version;
    }

    /**
     * Flag to load SQLCipher native libs (default: true).
     */
    public void setLoadSQLCipherNativeLibs(boolean loadSQLCipherNativeLibs) {
        this.loadSQLCipherNativeLibs = loadSQLCipherNativeLibs;
    }

    /**
     * Like {@link #getWritableDatabase()}, but returns a greenDAO abstraction of the database.
     * The backing DB is an standard {@link SQLiteDatabase}.
     */
    public Database getWritableDb() {
        return wrap(getWritableDatabase());
    }

    /**
     * Like {@link #getReadableDatabase()}, but returns a greenDAO abstraction of the database.
     * The backing DB is an standard {@link SQLiteDatabase}.
     */
    public Database getReadableDb() {
        return wrap(getReadableDatabase());
    }

    protected Database wrap(SQLiteDatabase sqLiteDatabase) {
        return new StandardDatabase(sqLiteDatabase);
    }

    /**
     * Delegates to {@link #onCreate(Database)}, which uses greenDAO's database abstraction.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        onCreate(wrap(db));
    }

    /**
     * Override this if you do not want to depend on {@link SQLiteDatabase}.
     */
    public void onCreate(Database db) {
        // Do nothing by default
    }

    /**
     * Delegates to {@link #onUpgrade(Database, int, int)}, which uses greenDAO's database abstraction.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(wrap(db), oldVersion, newVersion);
    }

    /**
     * Override this if you do not want to depend on {@link SQLiteDatabase}.
     */
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        // Do nothing by default
    }

    /**
     * Delegates to {@link #onOpen(Database)}, which uses greenDAO's database abstraction.
     */
    @Override
    public void onOpen(SQLiteDatabase db) {
        onOpen(wrap(db));
    }

    /**
     * Override this if you do not want to depend on {@link SQLiteDatabase}.
     */
    public void onOpen(Database db) {
        // Do nothing by default
    }

    private EncryptedHelper checkEncryptedHelper() {
        if (encryptedHelper == null) {
            encryptedHelper = new EncryptedHelper(context, name, version, loadSQLCipherNativeLibs);
        }
        return encryptedHelper;
    }

    /**
     * Use this to initialize an encrypted SQLCipher database.
     *
     * @see #onCreate(Database)
     * @see #onUpgrade(Database, int, int)
     */
    public Database getEncryptedWritableDb(String password) {
        EncryptedHelper encryptedHelper = checkEncryptedHelper();
        return encryptedHelper.wrap(encryptedHelper.getWritableDatabase(password));
    }

    /**
     * Use this to initialize an encrypted SQLCipher database.
     *
     * @see #onCreate(Database)
     * @see #onUpgrade(Database, int, int)
     */
    public Database getEncryptedWritableDb(char[] password) {
        EncryptedHelper encryptedHelper = checkEncryptedHelper();
        return encryptedHelper.wrap(encryptedHelper.getWritableDatabase(password));
    }

    /**
     * Use this to initialize an encrypted SQLCipher database.
     *
     * @see #onCreate(Database)
     * @see #onUpgrade(Database, int, int)
     */
    public Database getEncryptedReadableDb(String password) {
        EncryptedHelper encryptedHelper = checkEncryptedHelper();
        return encryptedHelper.wrap(encryptedHelper.getReadableDatabase(password));
    }

    /**
     * Use this to initialize an encrypted SQLCipher database.
     *
     * @see #onCreate(Database)
     * @see #onUpgrade(Database, int, int)
     */
    public Database getEncryptedReadableDb(char[] password) {
        EncryptedHelper encryptedHelper = checkEncryptedHelper();
        return encryptedHelper.wrap(encryptedHelper.getReadableDatabase(password));
    }

    private class EncryptedHelper extends net.sqlcipher.database.SQLiteOpenHelper {
        public EncryptedHelper(Context context, String name, int version, boolean loadLibs) {
            super(context, name, null, version);
            if (loadLibs) {
                net.sqlcipher.database.SQLiteDatabase.loadLibs(context);
            }
        }

        @Override
        public void onCreate(net.sqlcipher.database.SQLiteDatabase db) {
            DatabaseOpenHelper.this.onCreate(wrap(db));
        }

        @Override
        public void onUpgrade(net.sqlcipher.database.SQLiteDatabase db, int oldVersion, int newVersion) {
            DatabaseOpenHelper.this.onUpgrade(wrap(db), oldVersion, newVersion);
        }

        @Override
        public void onOpen(net.sqlcipher.database.SQLiteDatabase db) {
            DatabaseOpenHelper.this.onOpen(wrap(db));
        }

        protected Database wrap(net.sqlcipher.database.SQLiteDatabase sqLiteDatabase) {
            return new EncryptedDatabase(sqLiteDatabase);
        }

    }
}

这个类乍一看真是又臭又长,找重点,
第一父类还是SQLiteOpenHelper,也就是其实所有的数据库调用创建和升级的形式依旧逃不过安卓提供的机制

第二创建函数和升级函数,看看升级函数是啥onUpgrade,只是在原来基础上做了一次重写

/**
     * Delegates to {@link #onUpgrade(Database, int, int)}, which uses greenDAO's database abstraction.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(wrap(db), oldVersion, newVersion);
    }

    /**
     * Override this if you do not want to depend on {@link SQLiteDatabase}.
     */
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        // Do nothing by default
    }

但是在这个函数 public void onUpgrade(Database db, int oldVersion, int newVersion) {}中并没有具体的实现体,然后回过头来再看子类有没有具体的实现,再一看,不就是DevOpenHelper中的升级方法么。

@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);
        }

现在应该知道onUpgrade的调用流程了吧。。。。

从数据库怎么的用法中讲如何修改升级

如何避免删库

官方开源代码中的example是这样写的

// regular SQLite database
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db");
        Database db = helper.getWritableDb();

        // encrypted SQLCipher database
        // note: you need to add SQLCipher to your dependencies, check the build.gradle file
        // DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db-encrypted");
        // Database db = helper.getEncryptedWritableDb("encryption-key");

        daoSession = new DaoMaster(db).newSession();

但是我对它的使用其实做了一点修改

// regular SQLite database
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db"){
            public void onUpgrade(Database db, int oldVersion, int newVersion){
                super.onUpgrade(db,oldVersion,newVersion);
            }
        };

现在可以看到吧,其实在拿到helper对象的时候,就是可以去去监听具体的升级回调以及升级函数的。运用java基础知识,如果我不想用父类的方法,这个时候我改怎么办???,直接注释掉不就行了啊。这样数据库版本号修改的时候,最起码不会删库了吧

删库问题解决了,那么我怎么升级啊???

先修改build.gradle下的数据库版本号

greendao{
        schemaVersion 3
        daoPackage 'com.example.test'
        targetGenDir 'src/main/java'
}

当数据库检测到schemaVersion版本号上升之后,这个时候就会触发onUpgrade函数,然后在函数方法内部做sql

// regular SQLite database
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db"){
            public void onUpgrade(Database db, int oldVersion, int newVersion){
                db.execSQL(StringHandler.append("ALTER TABLE"," user_info"," ADD ","type"," TEXT "));
            }
        };

直接在onUpgrade中执行我要修改的表的信息没毛病吧??这样不就可以对表进行改变了,而且不会删库。。

但是这样写又面临一个问题,就是如果我每次升级的话,那不是每次都会执行啊???
确实是这样的,所以在数据升级的过程中,可能我们自己需要比数据库更要只要当前版本号,和未来升级的版本号是多少

正确代码应该如下所示:

// regular SQLite database
        DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "notes-db"){
            public void onUpgrade(Database db, int oldVersion, int newVersion){
            	int currentVersion = 0;
            	if(oldVersion == 3){
            		//修改表结构信息
                	db.execSQL(StringHandler.append("ALTER TABLE"," user_info"," ADD ","type"," TEXT "));
                	currentVersion = newVersion;
                }
                if(oldVersion == 4){
                	//修改表结构信息
					db.execSQL(StringHandler.append("ALTER TABLE"," user_info"," ADD ","num"," TEXT "));
                	currentVersion = newVersion;
				}
				if(oldVersion == 5){
					PersonDao.createTable(db,true);
					currentVersion = newVersion;
				}
				if(currentVersion != newVersion){
					super.onUpgrade(db,oldVersion,newVersion);
				}
            }
        };

以上代码主要就是当前的版本号,升级之后的版本号,进行判断,从而决定执行的sql类型,避免在升级过程中执行到错误的sql语句,
从代码里面其实可以看到,super的方法永远不会走,因为每次升级的时候,currentVersion应该都是和newVersion相等的,其实这部分留在这,也是为了给自己一个提示吧。

然后到了一个很关键的东西,修改实体类

就是我通过onUpgrade升级之后,表也改了,GreenDao提供的实体类存取性质还存在么??

答案当然是存在的

修改表结构信息

答案是当然是存在的,GreenDao是一整个从编译到运行都提供了完整的方案,
首先把你要添加的实体类的字段与在onUpgrade中的数据字段操作保持一个,如在上面sql中我给user_info添加了一个text属性,那么我在修改UserInfo这个实体类的时候,我就需要添加一个String num的属性。

String num;

自动化编译开始:CTRL+F9,自动在UserInfo这个类中添加响应函数,以及必要信息。自动编译生成UserInfoDao,这个时候升级完成的数据表的字段,与实体类的字段就是一个一一对应的关系。

添加表

那添加表应该怎么操作,
操作步骤如上,第一步先自建一个实体类,比如Person这个类,然后完成GreenDao建表的必要不步骤
第二部自动化编译开始:CTRL+F9,自动在Book这个类中添加相应函数,以及必要信息。自动编译生成PersonDao
这个时候会再BookDao中生成静态函数

@Entity(nameInDb = "person")
public class Person{
	 @Id(autoincrement = true)
    private Long id;
    @Property(nameInDb = "name")
    private String name1;
    private int age;
    @Transient
    private int gender;
}

在生成的PersonDao中的静态函数

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

建表函数,有了这个建表函数,其实我们就可以直接在onUpgrade中直接进行调用了啊,何必费事自己写Create table 语句

if(oldVersion == 5){
					PersonDao.createTable(db,true);
					currentVersion = newVersion;
				}

这个时候升级的表也已经建立好了,数据库表信息也没删,然后又保持了GreenDao中具体的接口的完整性。

升级步骤整理

我简单整理了下升级需要操作顺序性啊:
1:要知道升级什么,就需要先对实体类进行优化,修改哪个字段,增加什么样的表格

2:CTRL+F9 这个就不解释了,调用GreenDao插件,然后生成必要的Dao文件,Master等等

3:查看之前的版本,以及即将要升级的版本号,重写onUpgrade方法也是最重要的,然后通过前后版本号的区分,执行不同的sql语句,达到对数据库表以及结构的修改

4:当然是修改build.gradle中的schemaVersion 1 //数据库版本号

以上方法亲测过,没遇到什么问题,你再也不需要担心数据表丢失。