Android:升级数据库版本并添加新表


117

我已经为我的应用程序创建了sqlite表,但是现在我想向数据库中添加一个新表。

我如下更改了数据库版本

private static final int DATABASE_VERSION = 2;

并添加了字符串以创建表

private static final String DATABASE_CREATE_color = 
   "CREATE TABLE IF NOT EXISTS files(color text, incident_id text)";

onCreateonUpgrade如下所示:

@Override
    public void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE_incident);
        database.execSQL(DATABASE_CREATE_audio);
        database.execSQL(DATABASE_CREATE_video);
        database.execSQL(DATABASE_CREATE_image);

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //drop table and add new tables when version 2 released.
        db.execSQL(DATABASE_CREATE_color);

    }

但是由于某种原因,没有创建新表。我究竟做错了什么?


是另一个有趣的解决方案,但是到目前为止,我所看到的最强大的版本在这里
Suragch '18

Answers:


280

1.关于onCreate()和onUpgrade()

onCreate(..)刚安装应用程序时会调用。 onUpgrade每当升级和启动应用程序且数据库版本不同时,都会调用。

2.增加数据库版本

您需要一个类似的构造函数:

MyOpenHelper(Context context) {
   super(context, "dbname", null, 2); // 2 is the database version
}

重要提示:仅增加应用程序版本是不够的onUpgrade

3.不要忘记您的新用户!

不要忘记添加

database.execSQL(DATABASE_CREATE_color);

以及onCreate()方法,否则新安装的应用程序将缺少该表。

4.如何处理随时间变化的多个数据库更改

当您连续进行应用程序升级时,其中一些已经进行数据库升级,您需要确保检查以下内容oldVersion

onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   switch(oldVersion) {
   case 1:
       db.execSQL(DATABASE_CREATE_color);
       // we want both updates, so no break statement here...
   case 2:
       db.execSQL(DATABASE_CREATE_someothertable); 
   }
}

这样,当用户从版本1升级到版本3时,他们将获得两个更新。当用户从版本2升级到版本3时,他们只会获得版本3的更新...毕竟,每次发布更新时,您都无法指望100%的用户群进行升级。有时他们跳过更新或12 :)

5.在开发过程中控制修订版本号

最后...打电话

adb uninstall <yourpackagename>

完全卸载该应用程序。再次安装时,一定会命中onCreate它,这使您不必在开发时就将数据库版本不断增加到平流层中。


5
关于#4:使用oldVersion传递的参数不是更好的主意吗?如果有任何升级语句是可重复的,那么您最终可能会在一个最新的数据库上重复它们。如果其中一条语句是截断一个表,那将是非常糟糕的。
格雷森,

3
@格雷森:太好了!老实说,我从未真正考虑过,这让我感到有些愚蠢。有时我认为我们习惯于使用想要的参数,而忽略其余参数!
jkschneider 2011年

1
您控制数据库,为什么要更改名称?
jkschneider 2015年

3
newVersion是没有用的,因为您总是在构造函数中始终设置当前数据库版本(请参阅第2部分),并且它将始终匹配。此处的关键思想是,您不希望仅从用户直接升级到的地方newVersion而不会在其间进行所有其他增量升级。
jkschneider

2
@kai CREATE_READINGS逻辑永远不要放在onUpgrade中,因为它在onCreate您的第一个版本的方法中。将onUpgrade切换中的情况视为“我正在从中升级oldVersion”。如果要从版本1升级,则不会创建读数表,因为它应该已经存在。希望这是有道理的……
jkschneider '16

9

您的代码看起来正确。我的建议是数据库已经认为已升级。如果您在增加版本号之后但在添加版本号之前执行了项目execSQL调用,则测试设备/仿真器上的数据库可能已经认为该版本为2。

一种快速的验证方法是将版本号更改为3-如果在此之后进行升级,您知道这仅仅是因为您的设备认为它已经升级。


然后,如预期的那样,您的代码很好。而不是在增量运行时。记住要onCreate()像jkschneider指出的那样添加表创建。
格雷森

2

您可以使用SQLiteOpenHelper的onUpgrade方法。在onUpgrade方法中,您将oldVersion作为参数之一。

onUpgrade使用中switch,每个中case使用版本号来跟踪数据库的当前版本。

最好从循环oldVersionnewVersion,递增version每次 1,然后逐步升级数据库。当具有数据库版本1的人在长时间后将应用程序升级到使用数据库版本7的版本,并且由于某些不兼容的更改而导致应用程序崩溃时,这将非常有用。

然后,将逐步完成数据库中的更新,涵盖所有可能的情况,即,将数据库中所做的更改合并到每个新版本中,从而防止应用程序崩溃。

例如:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (oldVersion) {
    case 1:
        String sql = "ALTER TABLE " + TABLE_SECRET + " ADD COLUMN " + "name_of_column_to_be_added" + " INTEGER";
        db.execSQL(sql);
        break;

    case 2:
        String sql = "SOME_QUERY";
        db.execSQL(sql);
        break;
    }

}

如果您删除这些break语句,你不会需要一个循环
塔什Pemhiwa

但oldVersion必须在每种情况下都增加,才能通过下一种情况@TashPemhiwa
Beulah Ana

switch语句需要中断的原因是可以同时运行多个案例-即使案例条件不满足,情况也会如此,@ BeulahAna
Tash Pemhiwa,

如果添加break并且某些数据库具有较旧或较新的版本,则查询可能会失败,因此不需要break,例如alter table(如果某些数据库版本中的某些列已更改),则根据数据库版本的丢失顺序,您的查询可能会失败
Neeraj辛格

2

@jkschneider的答案是正确的。但是,有更好的方法。

如链接https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/中所述,在每个更新的sql文件中写入所需的更改。

from_1_to_2.sql

ALTER TABLE books ADD COLUMN book_rating INTEGER;

from_2_to_3.sql

ALTER TABLE books RENAME TO book_information;

from_3_to_4.sql

ALTER TABLE book_information ADD COLUMN calculated_pages_times_rating INTEGER;
UPDATE book_information SET calculated_pages_times_rating = (book_pages * book_rating) ;

这些.sql文件将根据数据库的版本以onUpgrade()方法执行。

DatabaseHelper.java

public class DatabaseHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 4;

    private static final String DATABASE_NAME = "database.db";
    private static final String TAG = DatabaseHelper.class.getName();

    private static DatabaseHelper mInstance = null;
    private final Context context;

    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public static synchronized DatabaseHelper getInstance(Context ctx) {
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(BookEntry.SQL_CREATE_BOOK_ENTRY_TABLE);
        // The rest of your create scripts go here.

    }


    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.e(TAG, "Updating table from " + oldVersion + " to " + newVersion);
        // You will not need to modify this unless you need to do some android specific things.
        // When upgrading the database, all you need to do is add a file to the assets folder and name it:
        // from_1_to_2.sql with the version that you are upgrading to as the last version.
        try {
            for (int i = oldVersion; i < newVersion; ++i) {
                String migrationName = String.format("from_%d_to_%d.sql", i, (i + 1));
                Log.d(TAG, "Looking for migration file: " + migrationName);
                readAndExecuteSQLScript(db, context, migrationName);
            }
        } catch (Exception exception) {
            Log.e(TAG, "Exception running upgrade script:", exception);
        }

    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    private void readAndExecuteSQLScript(SQLiteDatabase db, Context ctx, String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            Log.d(TAG, "SQL script file name is empty");
            return;
        }

        Log.d(TAG, "Script found. Executing...");
        AssetManager assetManager = ctx.getAssets();
        BufferedReader reader = null;

        try {
            InputStream is = assetManager.open(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            reader = new BufferedReader(isr);
            executeSQLScript(db, reader);
        } catch (IOException e) {
            Log.e(TAG, "IOException:", e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException:", e);
                }
            }
        }

    }

    private void executeSQLScript(SQLiteDatabase db, BufferedReader reader) throws IOException {
        String line;
        StringBuilder statement = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            statement.append(line);
            statement.append("\n");
            if (line.endsWith(";")) {
                db.execSQL(statement.toString());
                statement = new StringBuilder();
            }
        }
    }
}

在同一链接中还提供了一个示例项目:https : //github.com/riggaroo/AndroidDatabaseUpgrades


1
我正要来这里写下同样的建议。很高兴您已经做到了。人们一定应该阅读您链接的文章。这也是Android SQLiteAssetHelper建议升级的内容。这也是CL。(这里 SQLite专家介绍Stack Overflow)建议使用
Suragch '18

此评论是我想要的。sql脚本+1
蓝色软件

1

处理数据库版本是应用程序开发中非常重要的部分。我假设您已经拥有AppDbHelper扩展类SQLiteOpenHelper。当您扩展它时,您将需要实现onCreateonUpgrade方法。

  1. 什么时候onCreateonUpgrade方法调用

    • onCreate 新安装应用程序时调用。
    • onUpgrade 应用更新时调用。
  2. 组织数据库版本我以类方法管理版本。创建接口迁移的实现。例如,对于第一个版本的创建MigrationV1类,第二个版本的创建MigrationV1ToV2(这些是我的命名约定)


    public interface Migration {
        void run(SQLiteDatabase db);//create tables, alter tables
    }

迁移示例:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. 使用迁移类

onCreate:由于onCreate将在全新安装应用程序时被调用,因此我们还需要执行所有迁移(数据库版本更新)。因此onCreate将如下所示:

public void onCreate(SQLiteDatabase db){
        Migration mV1=new MigrationV1();
       //put your first database schema in this class
        mV1.run(db);
        Migration mV1ToV2=new MigrationV1ToV2();
        mV1ToV2.run(db);
        //other migration if any
  }

onUpgrade:当已安装应用程序并将其更新到新的应用程序版本时,将调用此方法。如果应用程序包含任何数据库更改,则将所有数据库更改放入新的Migration类中并增加数据库版本。

例如,假设用户安装了数据库版本为1的应用程序,现在数据库版本已更新为2(所有架构更新都保留在中MigrationV1ToV2)。现在,当应用程序升级时,我们需要通过应用数据库架构更改来升级数据库,MigrationV1ToV2如下所示:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) {
        //means old version is 1
        Migration migration = new MigrationV1ToV2();
        migration.run(db);
    }
    if (oldVersion < 3) {
        //means old version is 2
    }
}

注意:所有onUpgrade在数据库架构中进行的升级(在中提到)都应在onCreate

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.