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

Posted

技术标签:

【中文标题】Android:升级数据库版本并添加新表【英文标题】:Android: upgrading DB version and adding new table 【发布时间】:2011-12-29 08:02:33 【问题描述】:

我已经为我的应用创建了 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);

    

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

【问题讨论】:

This 是另一个有趣的解决方案,但到目前为止我见过的最强大的版本是here。 【参考方案1】:

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,这样您就不必在开发过程中不断增加数据库版本...

【讨论】:

关于#4:使用传递的oldVersion 参数不是更好吗?如果任何升级语句是可重复的,您最终可能会在大部分最新的数据库上重复它们。如果其中一个语句是截断表,那将是非常糟糕的。 @Greyson:好点子!老实说,我觉得有点愚蠢,因为我从来没有真正考虑过。有时我认为我们养成了使用我们想要的论点而忽略其他论点的习惯! 你控制了数据库,为什么要改名? newVersion 有点没用,因为您总是在构造函数中设置当前数据库版本(参见第 2 部分),它总是匹配的。这里的关键思想是,您不希望只是从用户所在的位置直接升级到 newVersion,而无需通过其间的所有其他增量升级。 @kai CREATE_READINGS 逻辑不应该在 onUpgrade 中,因为它在您的第一个版本的 onCreate 方法中。将onUpgrade 开关中的情况想象为“我正在从oldVersion 升级”。如果您从版本 1 升级,则不会创建读数表,因为它应该已经存在。希望这是有道理的......【参考方案2】:

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

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

【讨论】:

然后,正如预期的那样,您的代码很好;只是不是在增量运行时。请记住将表创建添加到onCreate(),就像 jkschneider 指出的那样。【参考方案3】:

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

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

最好从oldVersion 循环到newVersion,每次将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 语句,你就不需要循环了 但 oldVersion 必须在每种情况下增加才能通过下一个情况@TashPemhiwa switch 语句需要 break 的原因是可以一次运行多个 case - 即使不满足 case 条件也会出现这种情况,@BeulahAna 如果您添加中断并且某些数据库具有旧版本或最新版本,那么您的查询可能会失败,因此不需要中断示例更改表如果某些列已经在某些数据库版本中更改,那么您的查询可能会失败,因为每个数据库版本的丢失序列【参考方案4】:

@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

【讨论】:

我正要来这里写同样的建议。我很高兴你已经做到了。人们绝对应该阅读您链接到的文章。这也是Android SQLiteAssetHelper 推荐的升级方式。这也是CL.( Stack Overflow 上的 SQLite 专家)recommends。 这条评论是我要找的。 sql 脚本,+1【参考方案5】:

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

    onCreateonUpgrade 方法被调用时

    onCreate 新安装应用时调用。 onUpgrade 在应用更新时调用。

    组织数据库版本 我在类方法中管理版本。创建接口迁移的实现。例如。第一个版本创建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.
     
   
    使用迁移类

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:当应用程序已经安装并更新到新的应用程序版本时,将调用此方法。如果应用程序包含任何数据库更改,则将所有数据库更改放入新的迁移类并增加数据库版本。

例如,假设用户安装了数据库版本 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 中执行

【讨论】:

以上是关于Android:升级数据库版本并添加新表的主要内容,如果未能解决你的问题,请参考以下文章

(php)import .sql 并在表存在时添加列或创建新表

安卓应用版本升级时sqlit数据库升级

升级应用时 升级数据库版本---保留原来的数据

Android 数据库升级解决方案

iOS CoreData 版本升级和数据库迁移

将带有数据的新表包含到现有的 Debezium 连接器中