SQLite升级

血红的双手。 提交于 2020-03-10 05:50:08

升级方案

1.让用户将应用卸载然后再安装最新版本的app
2.对数据库进行升级
对于第一种方案,用户卸载老版本就会造成数据丢失,这样对于用户的体验性极差,不到万不得已的时候不要做。
我们倾向于选择第二项方案。

不同版本升级分析

3.1.Version1.0
当我们开发第一个版本数据库的时候,SQLiteOpenHelper的继承类里会走onCreate()方法,即 —->v1.0 走onCreate(),这时候并不涉及更新的方法。

3.2.Version2.0
当我们开发到第二个数据库版本的时候,分两种情况:
(1) 用户从1.0版本升级到2.0版本 SQLiteOpenHelper的继承类里会走onUpgrade()方法,不走onCreate()方法。即v1.0—->v2.0 走onUpgrade();
(2) 如果是用户直接安装v2.0 , SQLiteOpenHelper的继承类里会走onCreate()方法,不走onUpgrade()方法。即 —->v2.0 走onCreate()

3.3.Version3.0
(1) 用户从1.0版本升级到3.0版本 ,SQLiteOpenHelper的继承类里会走onUpgrade()方法,不走onCreate()方法。即v1.0—->v3.0 走onUpgrade()
(2) 用户从2.0版本升级到3.0版本 ,SQLiteOpenHelper的继承类里会走onUpgrade()方法,不走onCreate()方法。即v2.0—->v3.0 走onUpgrade()
(3) 如果是用户直接安装3.0 ,SQLiteOpenHelper的继承类里会走onCreate()方法,不走onUpgrade()方法。即 —->v3.0 走onCreate()
如下图
在这里插入图片描述

跨越版本的升级

如果应用程序发布了多个版本,以致出现了三个以上数据库版本, 如何确保所有的用户升级应用后数据库都能用呢?有两种方式:

方式一:确定 相邻版本 的差别,从版本1开始依次迭代更新,先执行v1到v2,再v2到v3……

方式二:为 每个版本 确定与现在数据库的差别,为每个case撰写专门的升级代码。

方式一的优点是每次更新数据库的时候只需要在onUpgrade方法的末尾加一段从上个版本升级到新版本的代码,易于理解和维护,缺点是当版本变多之后,多次迭代升级可能需要花费不少时间,增加用户等待;

方式二的优点则是可以保证每个版本的用户都可以在消耗最少的时间升级到最新的数据库而无需做无用的数据多次转存,缺点是强迫开发者记忆所有版本数据库的完整结构,且每次升级时onUpgrade方法都必须全部重写。 以上简单分析了两种方案的优缺点,它们可以说在花费时间上是刚好相反的,至于如何取舍,可能还需要结合具体情况分析。

数据库版本升级

如果是从v1.0升级到v2.0,则走onUpgrade,如果是直接安装v2.0,则走onCreate()

  • 比如从v1.0升级到v2.0 需要增加字段
/**
     * 新版本比旧版本高就会走这个方法
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        for (int i = oldVersion; i < newVersion; i++) {
            switch (i) {
                case DataBaseConfig.DATABASE_FIRST_VERSION:
                    addUpgradeToVersion2(db);//添加两个字段
                    break;
                default:
                    break;
            }
        }
    }
/**
     * 新版本升级的改动
     *
     * @param db
     */
    private void addUpgradeToVersion2(SQLiteDatabase db) {//注意添加两个字段时要分开写
        String sql1 = "ALTER TABLE " + DataBaseConfig.TABLE_TEST1 + " ADD COLUMN sex  varchar";
        String sql2 = "ALTER TABLE " + DataBaseConfig.TABLE_TEST1 + " ADD COLUMN address varchar";
        db.execSQL(sql1);
        db.execSQL(sql2);
    }

此时 DataBaseConfig.DATABASE_NEW_VERSION 为2 ,DataBaseConfig.DATABASE_FIRST_VERSION为1

还要再处理onCreate(),因为用户可能是直接安装V2.0
有两种方法:
(1)执行旧版数据库sql,则后面还要调用onUpgrade

@Override
    public void onCreate(SQLiteDatabase db) {
        try {
            db.execSQL(DataBaseConfig.FIRST_VERSION_INFO);
            onUpgrade(db,DataBaseConfig.DATABASE_FIRST_VERSION,DataBaseConfig.DATABASE_NEW_VERSION);
        } catch (Exception e) {
            Log.e(tag,"e=="+e);
        }
    }

(2)执行新版数据库sql,不需要调用onUpgrade

@Override
    public void onCreate(SQLiteDatabase db) {
        try {
            db.execSQL(DataBaseConfig.NEW_VERSION_INFO);
        } catch (Exception e) {
            Log.e(tag,"e=="+e);
        }
    }
 //增加两个字段
    String NEW_VERSION_INFO ="create table if not exists "+TABLE_TEST1+"( " +
            " id      varchar     DEFAULT ''," +
            " name    varchar     DEFAULT ''," +
            " age     varchar     DEFAULT '',"+
            " sex     varchar     DEFAULT '',"+
            " address  varchar     DEFAULT '')";
  • 第二个版本数据库需要新增并修改字段
    比如从v1.0升级到v2.0 需要增加字段
    新增sex,address字段,修改age–>sign
/**
     * 新版本比旧版本高就会走这个方法
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        for (int i = oldVersion; i < newVersion; i++) {
            switch (i) {
                case DataBaseConfig.DATABASE_FIRST_VERSION:
                    updateUpgradeToVersion2(db);//修改表原有字段并新增字段
                    break;
                default:
                    break;
            }
        }
    }
private void updateUpgradeToVersion2(SQLiteDatabase db) {
        try{
            db.beginTransaction();
            // 1, 将表TABLE_TEST1重命名为TABLE_TEST1+"_temp"
            String tempTableName = DataBaseConfig.TABLE_TEST1 + "_temp";
            String sql = "ALTER TABLE " + DataBaseConfig.TABLE_TEST1 + " RENAME TO " + tempTableName;
            db.execSQL(sql);
            // 2, 创建用户表(字段id、name改变、并新增了以个字段phone)
            db.execSQL(DataBaseConfig.NEW_VERSION_INFO);
            // 3, 将旧表数据导入到新表中,旧表中的id,name,age分别查到新表中的id,name,sign
            sql = "INSERT INTO " + DataBaseConfig.TABLE_TEST1 + " (" + "id,name,sign" + ") " + " SELECT " + "id,name,age" + " FROM " + tempTableName;
            db.execSQL(sql);
            // 4, 删除旧的表
            db.execSQL("DROP TABLE IF EXISTS " + tempTableName);
            db.setTransactionSuccessful();
            db.setVersion(2);

        }catch (SQLException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            db.endTransaction();
        }

    }

此时 DataBaseConfig.DATABASE_NEW_VERSION 为2 ,DataBaseConfig.DATABASE_FIRST_VERSION为1

SQLite数库对ALTER TABLE命令

SQLite数库对ALTER TABLE命令支持非常有限,只能在表末尾添加列,不能修改列定义,不能删除已有的列。那么如果要修改表呢?我们可以采用临时表的办法。具体来说有四步:

将现有表重命名为临时表;
创建新表;
将临时表的数据导入新表(注意处理修改的列);
删除临时表。
代码案例如下:

@Override
public void onCreate(SQLiteDatabase db) {
    createAllTables(db);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (oldVersion) {
        case 1:
        //如果老版本是1,则创建X表
        createTableX(db);
        case 2:
        //如果老版本是2,则修改表X的结构(例如添加两个字段)
        upgradeTables(db,"X","ColA, ColB, ColC, ... ColN");
            break;
        default:
            dropAllTables(db);
            createAllTables(db);
            break;
    }
}

upgradeTables方法里采用的是数据库事务,利用事务的原子特性(确保工作单位内的所有操作都成功完成,否则,事务会在出现故障时终止,之前的操作也会回滚到以前的状态)

protected void upgradeTables(SQLiteDatabase db, String tableName, String columns)  
{  
    try  
    {  
        db.beginTransaction();  

        // 1, Rename table.  
        String tempTableName = tableName + "_temp";  
        String sql = "ALTER TABLE " + tableName +" RENAME TO " + tempTableName;  
        execSQL(db, sql, null);  

        // 2, Create table.  
        //onCreateTable(db); 
        createNewTableX(db);

        // 3, Load data  
        sql =   "INSERT INTO " + tableName +  
                " (" + columns + ") " +  
                " SELECT " + columns + " FROM " + tempTableName;  

        execSQL(db, sql, null);  

        // 4, Drop the temporary table.  
        execSQL(db, "DROP TABLE IF EXISTS " + tempTableName, null);  

        db.setTransactionSuccessful();  
    }  
    catch (SQLException e)  
    {  
        e.printStackTrace();  
    }  
    catch (Exception e)  
    {  
        e.printStackTrace();  
    }  
    finally  
    {  
        db.endTransaction();  
    }  
}  

模拟数据库升级案例

  • 第一版程序,只创建一张Book表
public class MyDatabaseHelper extends SQLiteOpenHelper{
  //创建Book表的SQL语句
  public static final String CREATE_BOOK = "create table Book("
    + "id integer primary key autoincrement,"
    + "anthor text,"
    + "price real,"
    + "pages integer,"
    + "name text)";
  public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version){
    super(context, name, factory, version);
  }
  @override
  public void onCreate(SQLiteDatabase db){
    //执行SQL语句,创建Book表
    db.execSQL(CREATE_BOOK );
  }
  @override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
  }
}

这样当用户安装了第一版程序时,其存储空间上自然就有了Book表

  • 第二版程序,数据库中新增一个Category表
public class MyDatabaseHelper extends SQLiteOpenHelper{
  //创建Book表的SQL语句
  public static final String CREATE_BOOK = "create table Book("
    + "id integer primary key autoincrement,"
    + "anthor text,"
    + "price real,"
    + "pages integer,"
    + "name text)";

  //创建Category表的SQL语句
  public static final String CREATE_CATEGORY = "create table Category("
  + "id integer primary key autoincrement,"
  + "category_name text,"
   + "category_code integer";

  public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version){
    super(context, name, factory, version);
  }
  @override
  public void onCreate(SQLiteDatabase db){
    //执行SQL语句,创建Book表
    db.execSQL(CREATE_BOOK );
    //执行SQL语句,创建Category表
    db.execSQL(CREATE_CATEGORY );
  }
  @override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
    switch(oldVersion){
      case 1:
        db.execSQL(CREATE_CATEGORY);
      default;
    }
  }
}

在onCreate()方法中新增了一条创建Category表的语句:当用户没有使用个过这个程序时,直接安装了第二版的程序,首次使用的时候就会创建Book表和Category表,
在onUpgrade()方法中添加了一个switch判断,若用户使用了第一版的程序,再安装第二版的程序来覆盖第一版的程序,就会调用onUpgrade()方法,当旧版本的数据库版本是1,就会执行创建Category表的SQL语句

  • 第三版程序,Book表和Category表建立连接
public class MyDatabaseHelper extends SQLiteOpenHelper{
  //创建Book表的SQL语句
  public static final String CREATE_BOOK = "create table Book("
    + "id integer primary key autoincrement,"
    + "anthor text,"
    + "price real,"
    + "pages integer,"
    + "name text)";

  //创建Category表的SQL语句,较第二版程序比起来,新增了一个字段
  public static final String CREATE_CATEGORY = "create table Category("
  + "id integer primary key autoincrement,"
  + "category_name text,"
   + "category_code integer"
   + "category_id integer";

  public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version){
    super(context, name, factory, version);
  }
  @override
  public void onCreate(SQLiteDatabase db){
    //执行SQL语句,创建Book表
    db.execSQL(CREATE_BOOK );
    //执行SQL语句,创建Category表
    db.execSQL(CREATE_CATEGORY );
  }
  @override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion){
    switch(oldVersion){
      case 1:
        db.execSQL(CREATE_CATEGORY);
      case 2:
        db.execSQL("alter table Book add column category_id integer");
      default;
    }
  }
}

创建Category表的SQL语句新增了一个字段,当用户没有使用过本程序时,直接安装的是第三个版本的程序就会执行onCreate()方法,实现了创建两个数据库表的功能
当用户由第一个版本或者第二个版本升级到第三个版本的时候,就会执行onUpgrade()方法,注意没有break

Android Sqlite数据库升级时注意事项

先看下常用的SQLiteOpenHelper的方法:

public class DatabaseHelper extends SQLiteOpenHelper {

    public DatabaseHelper(Context context, String name, CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    /**
     * 1、在第一次打开数据库的时候才会走
     * 2、在清除数据之后再次运行-->打开数据库,这个方法会走
     * 3、没有清除数据,不会走这个方法
     * 4、数据库升级的时候这个方法不会走
     */
    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    /**
     * 1、这个方法只有当数据库已经存在,而且版本升高的时候,才会调用
     * 2、第一次创建数据库的时候,这个方法不会走
     * 3、清除数据后再次运行(相当于第一次创建)这个方法也不会走
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

}

如果第一次的数据库版本为1,现在升级之后版本为2,即当前app版本比上次的app版本中的数据库的版本号高1级,也有可能是3升4, 4升5这样的,先看下这时的处理有几种情况要考虑:

  • 摒弃之前的表并重新创建该表

这种情况一般是数据库中的数据可能是一些无关紧要的临时数据,处理比较简单粗暴,直接删除重建,且表的结构跟之前完全一样。不过应该很少有采用这种方式的吧。

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       switch (oldVersion) {
           case 1://这里的数值是上次的版本,也就是针对上次的版本,本次的版本要做哪些改变
                db.execSQL("DROP TABLE IF EXISTS " + TableEvent.TABLE_NAME);
                db.execSQL(TableEvent.CREATE_SQL);
           default:
                break;
        }
}
  • 在之前基础上创建新的表

这种情况也比较简单,主要是新版比之前的版本创建新的表,而且这个表跟之前的表没有什么关系。这时同时也要在onCreate中加上执行创建的sql(针对新用户)。

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       switch (oldVersion) {
           case 1://这里的数值是上次的版本,也就是针对上次的版本,本次的版本要做哪些改变
                //这种情况需要同时在onCreate中加上执行新表的创建sql(针对新用户)
                db.execSQL(xxxxx);
           default:
                break;
        }
}
  • 在老表基础上新增字段
    这种情况需要修改老的表新增字段
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       switch (oldVersion) {
           case 1://这里的数值是上次的版本,也就是针对上次的版本,本次的版本要做哪些改变
                //这种情况需要同时在onCreate中加上执行新增字段的创建表sql(针对新用户)
                db.execSQL("ALTER TABLE TableEvent.CREATE_SQL ADD COLUMN TableEvent.xxx VARCHAR(255)");
           default:
                break;
        }
}
  • 数据库升级时保留老的数据并同步到新的表当中
    这种情况是新表保留老表的字段结构情况
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
       switch (oldVersion) {
           case 1://这里的数值是上次的版本,也就是针对上次的版本,本次的版本要做哪些改变
                //老的表重命名
                String CREATE_TEMP_BOOK = "alter table book rename to _temp_book";
                //创建新的表,表名跟原来一样,并保留原来的字段
                String CREATE_BOOK = "create table book(bookId integer primarykey, bookName text);";
                //将重命名后的老表中的数据导入新的表中
                String INSERT_DATA = "insert into book select *,'' from _temp_book";
                //删除老表
                String DROP_BOOK = "drop table _temp_book";
                db.execSQL(CREATE_TEMP_BOOK);
                //这句要同时放到onCreate中,针对新用户
                db.execSQL(CREATE_BOOK);
                db.execSQL(INSERT_DATA);
                db.execSQL(DROP_BOOK);
           default:
                break;
        }
}
  • 新表跟以前的表字段结构完全不一样

这种情况是新表跟以前字段结构完全不一样或者说有大部分的差异,跟上面第4种处理差不多,但是需要手动查询老表的某些字段的数据然后插入的新表的对应字段,最后删除老表即可。

前面几种情况是单级升级的时候,针对上次的版本本次的版本需要做的处理,如果是跨级升级,比如用户app很久没有更新了,这次升级数据库版本从1升到了3,或者2升到了6,这时需要分开处理每次的版本:

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (oldVersion) {
        case 1:
                db.execSQL(CREATE_TBL_CATEGORY)  // 建立新表
        case 2:
                db.execSQL("ALTER TABLE Book ADD COLUMN category_id INTEGER");  // 增加字段
        case 3:
                //同步老的数据到新的表
                String CREATE_TEMP_BOOK = "alter table book rename to _temp_book";
                String CREATE_BOOK = "create table book(bookId integer primarykey, bookName text);";
                String INSERT_DATA = "insert into book select *,'' from _temp_book";
                String DROP_BOOK = "drop table _temp_book";
                db.execSQL(CREATE_TEMP_BOOK);
                db.execSQL(CREATE_BOOK);
                db.execSQL(INSERT_DATA);
                db.execSQL(DROP_BOOK);
        default:
            break;
    }
}

注意,这里OnUpgrade() 方法中的 switch 语句是没有 break 的,会一直执行到语句结束。为什么要这么写想想就明白了。比如用户手上的版本是 1,新版 App 的版本是 5,那么就会有 4 个版本的数据库升级,switch() 自然不能中途 break,必须执行这 4 个版本的数据库升级语句。同时每次的case中添加的创建新表的sql代码不要忘了在onCreate中同时添加,因为新用户也是要执行的。

getReadableDatabase()和getWriteableDatabase()的区别

  • 查看源码,发现getReadableDatabase()和getWriteableDatabase()都是调用getDatabaseLocked(boolean writeable) 方法,传不同的参数
  • getReadableDatabase() 会获取用于操作SQLiteDatabase的实例。 getReadableDatabase()会先以读写方式打开数据库,若数据库磁盘空间满了,打开失败,会继续尝试以只读方式打开。若磁盘空间有了,会关闭只读数据库对象,返回可读写数据库对象。
  • getWriteableDatabase()也是会以读写方式打开数据库,如果磁盘满了,会抛异常,不会返回数据库对象。
  • getReadableDatabase()会在抛异常的时候以只读模式打开数据库。而getWritableDatabase()不会
public SQLiteDatabase getWritableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(true);
        }
    }
 public SQLiteDatabase getReadableDatabase() {
        synchronized (this) {
            return getDatabaseLocked(false);
        }
    }

    private SQLiteDatabase getDatabaseLocked(boolean writable) {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // Darn!  The user closed the database by calling mDatabase.close().
                mDatabase = null;
            } else if (!writable || !mDatabase.isReadOnly()) {
                // The database is already open for business.
                return mDatabase;
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException("getDatabase called recursively");
        }

        SQLiteDatabase db = mDatabase;
        try {
            mIsInitializing = true;

            if (db != null) {
                if (writable && db.isReadOnly()) {
                    db.reopenReadWrite();
                }
            } else if (mName == null) {
                db = SQLiteDatabase.create(null);
            } else {
                try {
                    if (DEBUG_STRICT_READONLY && !writable) {
                    //如果以严格的只读方式打开
                    //获取db文件的路径
                        final String path = mContext.getDatabasePath(mName).getPath();
                        //直接打开数据库
                        db = SQLiteDatabase.openDatabase(path, mFactory,
                                SQLiteDatabase.OPEN_READONLY, mErrorHandler);
                    } else {
                    //如果以读写方式打开
                    //打开或创建数据库
                        db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
                                Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
                                mFactory, mErrorHandler);
                    }
                } catch (SQLiteException ex) {
                //getWritableDatabase()会在抛异常的时候抛出异常
                    if (writable) {
                        throw ex;
                    }
                    Log.e(TAG, "Couldn't open " + mName
                            + " for writing (will try read-only):", ex);
                    final String path = mContext.getDatabasePath(mName).getPath();
                    //getReadableDatabase()会在抛异常的时候以只读模式打开数据库
                    db = SQLiteDatabase.openDatabase(path, mFactory,
                            SQLiteDatabase.OPEN_READONLY, mErrorHandler);
                }
            }
    }

两种数据库升级的处理方法

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!