在架构中添加新列时,为什么需要更新数据库的版本号?

时间:2018-11-01 20:19:20

标签: android database sqlite

我是Udacity的一名学习Android Apps开发的人,当时我正在上数据库课程,指导老师告诉我们,当我们更改数据库架构时,我们需要更改DATABASE_VERSION。这让我很困惑,但找不到任何解决方案。拜托,有人给我解释一下。

谢谢

4 个答案:

答案 0 :(得分:1)

想象一下这种情况:

  1. 您创建了应用的初始版本。

  2. 人们安装了您的应用程序,并且在首次使用数据库时,方法onCreate()被执行,Android在内部存储器(例如test.db)上创建了数据库。

  3. 现在,您创建了一个新功能,该功能需要一个新列(更改了架构)。

  4. 将安装新版本应用程序的新用户很好,因为onCreate将在此新列中执行。

  5. 但是,那些已经在文件系统上创建数据库的老用户呢?

这就是为什么您需要一种机制来更新数据库。因为在新版本的应用程序中,您具有针对该新数据库架构的SQL查询。您使用该新列进行查询。但是,旧用户仍然具有数据库的旧版本。因此,他们还不存在该新列。

更新机制很简单:每次需要升级数据库时,只需增加数据库版本即可。 Android会自动检查文件系统上的数据库版本。如果安装的版本比当前数据库旧,则将调用onUpdate(),然后可以更新所需的方式:

  • 您可以删除旧表并创建一个新表。
  • 您可以创建新列而不删除旧数据。
  • 等等...这会因项目而异...会根据您的需要而有所不同。例如,您可能从未使用过。

如果您尚未发布APK,则无需更改数据库版本(因为尚未安装您的应用)。只需不断进行所需的更改即可。但是,在发布应用程序之后,您始终必须格外小心数据库版本及其更新方式...您始终必须使用全新安装来模拟测试,并模拟正在更新应用程序的用户... < / p>

答案 1 :(得分:1)

  

为什么在以下情况下需要更新数据库的版本号   在架构中添加新列?

不必更新版本号,而是一种建议的方法,该方法基于将代码中提供的版本号与用户版本字段中存储的值进行检查,从而引入结构更改数据库文件头的大小(偏移量60处为4个字节),但不是唯一的方法。这很方便。

仅当使用子类(扩展)SQLiteOpenHelper类时才适用/可用。您不需要使用此类子类,因为您可以使用SQLiteDatabase openDatabase方法(在这种情况下,既不是 onCreate 也不是 onUpgrade (甚至很少使用的 onDowngrade )方法将被调用甚至可用)。

您可以通过一些工作来实现替代方案,例如通过 sqlite_master 表检查/比较结构以及针对模式进行编译指示的过程。您可以实现使用表来跟踪结构更改的过程。您可能有一个空的数据库并将其用作模型(尽管这可能会浪费磁盘空间)。

例如,基于添加一列,您可以有一个给定表和列定义的方法,它检查表中是否存在该列(例如,检查PRAGMA table_info的结果),然后应用 ALTER 设置。

通常,混淆之处不在于 onUpgrade 方法,而在于对 onCreate 方法的误解。也就是说 onCreate 方法不会在每次运行App时运行, onCreate 方法仅在创建数据库时自动运行,除非数据库随后被删除,否则该方法只能运行一次(在这种情况下,它将再次运行)。

  • 这种困惑很可能源于人们经常在用于初始化/设置值的活动中看到 onCreate 方法。

因此,这样的应用可能需要一种引入结构更改的方法,因为 onCreate 不会运行(除非强制执行,例如从内部调用 onCreate 方法的常用方法 onUpgrade 方法)。

演示

  • 作为基本App的代码更改了数据库结构 每次运行该应用程序以模拟多个版本时(前4个 运行,则结构将稳定)。

  • 它不打算在真正的App中使用。

  • 大多数结构更改不是通过 onUpgrade 完成的 方法。但是,第四轮运行确实改变了结构(增加了一个 表中的列)通过 onUpgrade 方法。

数据库助手(SQLiteOpenHelper的子类):-

public class TestDBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "testdb";

    public static final String TBNAME_001 = "test001_table";
    public static final String TBNAME_002 = "test002_table";
    public static final String TBNAME_003 = "test003_table";
    public static final String COL_NAME = "name_column";
    public static final String COL_EXTRA = "extra_cxolumn";

    String TAG = "TESTDBHLPR";

    public TestDBHelper(Context context) {
        //<<<<<<<<<<Note gets database version according to value in MainActivity>>>>>>>>>>
        super(context, DBNAME, null, MainActivity.getDatabaseVersion());
        Log.d(TAG,"CONSTRUCTOR invoked.");
        this.getWritableDatabase(); //<<<<< Force  database open
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG,"ONCREATE invoked.");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int old_version, int new_version) {
        Log.d(TAG,"ONUPGRADE invoked. " +
                "\n\tOld version (valued stored in DB) is " + String.valueOf(old_version) +
                "\n\tNew version (as coded in the App) is " + String.valueOf(new_version)
        );
        if (new_version == 2) {
            db.execSQL(getAlterSQl(TBNAME_003));
        }
    }

    public static String getAlterSQl(String table_name) {
        return "ALTER TABLE " + table_name + " ADD COLUMN " + COL_EXTRA + " TEXT";
    }
}
  • 请注意, onCreate 除了将输出写入日志外什么也不做。
    • 每次运行该应用程序时,将逐步添加表。
  • onUpgrade 仅在版本号更改为2时会执行某些操作。
  • getAlterSQL方法仅返回字符串 ALTER TABLE ?????添加栏外文字
  • 请注意如何从MainActivity获取数据库版本(以便可以随时更改版本以进行演示)。

以下是调用活动 MainActivity.java

的代码
public class MainActivity extends AppCompatActivity {

    TestDBHelper mDBHlpr; //<<<<<<<<<< Declare the Database Helper (will at this stage be NULL)
    Context mContext;
    static int mDatabaseVersion = 1; //<<<<<<<<<< DBVERSION can be changed on the fly
    String TAG = "MAINACTIVITY";

    @Override
        protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;

        addNewTable(); //<<<<<<<<<< update database structure if needed

        Log.d(TAG,"Instantiating the Database helper");
        mDBHlpr = new TestDBHelper(this);
        logDBInfo(mDBHlpr.getWritableDatabase());
    }

    public static int getDatabaseVersion() {
        return mDatabaseVersion;
    }

    private void addNewTable() {
        String TAG = "ADDNEWTABLE";
        File db_file = this.getDatabasePath(TestDBHelper.DBNAME);
        if (!db_file.exists()) {
            Log.d(TAG,"Database doesn't exist so exiting.");
            return;
        }
        Log.d(TAG,"Database file exists. Checking for table");
        SQLiteDatabase db = SQLiteDatabase.openDatabase(
                this.getDatabasePath(TestDBHelper.DBNAME).getPath(),
                null,
                SQLiteDatabase.OPEN_READWRITE,
                null
        );
        Log.d(TAG,"Writing Database info (if any) as before any changes");
        logDBInfo(db);
        Log.d(TAG,"Database existed and has been opened");
        String whereclause = "type='table' AND tbl_name LIKE 'test%'";
        Cursor csr = db.query("sqlite_master",null,whereclause,null,null,null,null);
        int row_count = csr.getCount();
        Log.d(TAG,"Extracted " + String.valueOf(row_count) + " application tables");
        csr.close();
        String table_name = "x"; //<<<<<
        switch (row_count) {
            case 0:
                table_name = TestDBHelper.TBNAME_001;
                break;
            case 1:
                table_name = TestDBHelper.TBNAME_002;
                Log.d(TAG,"Adding column " + TestDBHelper.COL_EXTRA + " to table " + TestDBHelper.TBNAME_001);
                db.execSQL(TestDBHelper.getAlterSQl(TestDBHelper.TBNAME_001));
                break;
            case 2:
                table_name = TestDBHelper.TBNAME_003;
                mDatabaseVersion = 2; //<<<<<<<<<< Force onUpgrade
                break;
            default:
                mDatabaseVersion = 2;
        }
        if (table_name.length() < 2) {
            Log.d(TAG,"Database exists but nothing to do");
            return;
        }
        Log.d(TAG,"Creating table " + table_name);
        String crt_sql = "CREATE TABLE IF NOT EXISTS " + table_name + "(" +
                TestDBHelper.COL_NAME + " TEXT" +
                ")";
        db.execSQL(crt_sql);
        Log.d(TAG,"Writing Database info (if any) as after any changes");
        logDBInfo(db);
        db.close();
    }

    private void logDBInfo(SQLiteDatabase db) {
        String TAG = "DBINFO";
        Cursor csr = db.query("sqlite_master",null,null,null,null,null,null);
        while (csr.moveToNext()) {
            String type = csr.getString(csr.getColumnIndex("type"));
            String table_name = csr.getString(csr.getColumnIndex("tbl_name"));
            Log.d(TAG,"Type is " + type + " for table " + table_name);
            if (type.equals("table")) {
                Cursor csr2 = db.rawQuery("PRAGMA table_info(" + table_name + ")",null);
                while (csr2.moveToNext()) {
                    Log.d(TAG,"\n\tTable has a column named " + csr.getString(csr2.getColumnIndex("name")));
                }
                csr2.close();
            }
        }
        csr.close();
    }
}

在实例化数据库助手之前运行该应用程序时,将运行 addNewTable 方法。

addNewTable 方法根据数据库中存在的内容执行不同的操作。

第一次运行

如果是第一次运行该应用程序(或已删除数据库/卸载了应用程序),则该方法仅返回。

然后在实例化数据库助手(通过this.getWritableDatabase(); //<<<<< Force database open)时创建数据库。

因此调用了 onCreate 方法,但它并没有在结构上做任何事情。

最后调用logDBInfo方法列出表(请注意,创建数据库时会创建表 android_metadata ,因此将其列出,这是一个特定于Android的表,其中包含语言环境,通常可以忽略)。

日志(由于添加了大量日志)显示:-

11-02 18:45:02.689 2066-2066/axtest.axtest D/ADDNEWTABLE: Database doesn't exist so exiting.
11-02 18:45:02.689 2066-2066/axtest.axtest D/MAINACTIVITY: Instantiating the Database helper
11-02 18:45:02.689 2066-2066/axtest.axtest D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:45:02.701 2066-2066/axtest.axtest D/TESTDBHLPR: ONCREATE invoked.
11-02 18:45:02.701 2066-2066/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:45:02.701 2066-2066/axtest.axtest D/DBINFO:    Table has a column named locale

第二次运行

由于在调用 addNewTable 时数据库已存在,因此它不会返回,而是打开数据库(不使用数据库助手),然后记录数据库信息(在应用任何更改之前。)

然后在 sqlite_master 表(包含有关项目数据的SQLite内部/系统表)上运行查询,并提取以 test 开头的表的行(都不是,因为存在的唯一表是 sqlite_master android_metadata )。

获得与表数相等的行数。对于第二次运行,它将为0。

开关/案例相应地设置表名,为此,将表名设置为常量 TABLE001 所保存的值,该值(由于结果值的长度大于1)然后用于创建表名。桌子。

创建表后,将记录数据库信息,现在显示新表并关闭了数据库。

然后实例化数据库帮助程序,因为该数据库现在存在,所以不调用 onCreate ,因为版本为1,所以不调用 onUpgrade 。最后,数据库信息为已记录。

日志显示:-

11-02 18:46:16.009 2109-2109/axtest.axtest D/ADDNEWTABLE: Database file exists. Checking for table
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as before any changes
11-02 18:46:16.013 2109-2109/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:46:16.013 2109-2109/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Database existed and has been opened
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Extracted 0 application tables
11-02 18:46:16.013 2109-2109/axtest.axtest D/ADDNEWTABLE: Creating table test001_table
11-02 18:46:16.021 2109-2109/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as after any changes
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:46:16.021 2109-2109/axtest.axtest D/MAINACTIVITY: Instantiating the Database helper
11-02 18:46:16.021 2109-2109/axtest.axtest D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:46:16.021 2109-2109/axtest.axtest D/DBINFO:    Table has a column named name_column

第三次比赛

这类似于第二次运行,只是添加了 test002_table ,而不是test001_table,并且该表 tes001_table 添加了名为 extra_column

日志显示:-

11-02 18:50:13.925 2160-2160/axtest.axtest D/ADDNEWTABLE: Database file exists. Checking for table
11-02 18:50:13.929 2160-2160/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as before any changes
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:50:13.933 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.933 2160-2160/axtest.axtest D/ADDNEWTABLE: Database existed and has been opened
11-02 18:50:13.933 2160-2160/axtest.axtest D/ADDNEWTABLE: Extracted 1 application tables
11-02 18:50:13.937 2160-2160/axtest.axtest D/ADDNEWTABLE: Adding column extra_column to table test001_table
11-02 18:50:13.937 2160-2160/axtest.axtest D/ADDNEWTABLE: Creating table test002_table
11-02 18:50:13.941 2160-2160/axtest.axtest D/ADDNEWTABLE: Writing Database info (if any) as after any changes
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named extra_column
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO: Type is table for table test002_table
11-02 18:50:13.941 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.941 2160-2160/axtest.axtest D/MAINACTIVITY: Instantiating the Database helper
11-02 18:50:13.941 2160-2160/axtest.axtest D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO: Type is table for table android_metadata
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named locale
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO: Type is table for table test001_table
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named extra_column
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO: Type is table for table test002_table
11-02 18:50:13.945 2160-2160/axtest.axtest D/DBINFO:    Table has a column named name_column

第四次奔跑

第四次运行添加了另一个表,即 test003_table ,但现在在实例化数据库帮助器之前将数据库版本从1更改为2,从而最终导致 onUpGrade 方法成为这称为 test003_table ,方法是添加一列,即 extra_column

日志显示:-

11-02 18:57:00.589 2230-2230/? D/ADDNEWTABLE: Database file exists. Checking for table
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Writing Database info (if any) as before any changes
11-02 18:57:00.593 2230-2230/? D/DBINFO: Type is table for table android_metadata
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named locale
11-02 18:57:00.593 2230-2230/? D/DBINFO: Type is table for table test001_table
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named extra_column
11-02 18:57:00.593 2230-2230/? D/DBINFO: Type is table for table test002_table
11-02 18:57:00.593 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Database existed and has been opened
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Extracted 2 application tables
11-02 18:57:00.593 2230-2230/? D/ADDNEWTABLE: Creating table test003_table
11-02 18:57:00.597 2230-2230/? D/ADDNEWTABLE: Writing Database info (if any) as after any changes
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table android_metadata
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named locale
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table test001_table
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named extra_column
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table test002_table
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.597 2230-2230/? D/DBINFO: Type is table for table test003_table
11-02 18:57:00.597 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.597 2230-2230/? D/MAINACTIVITY: Instantiating the Database helper
11-02 18:57:00.597 2230-2230/? D/TESTDBHLPR: CONSTRUCTOR invoked.
11-02 18:57:00.601 2230-2230/? D/TESTDBHLPR: ONUPGRADE invoked. 
        Old version (valued stored in DB) is 1
        New version (as coded in the App) is 2
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table android_metadata
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named locale
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table test001_table
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named extra_column
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table test002_table
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.605 2230-2230/? D/DBINFO: Type is table for table test003_table
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named name_column
11-02 18:57:00.605 2230-2230/? D/DBINFO:    Table has a column named extra_column

随后的运行将使用最终结构和数据库版本2。

  • 如果switch / case构造的默认值未将版本设置为2,因此使用了版本1,则它将失败,因为未定义 onDownGrade 方法。

答案 2 :(得分:0)

从根本上讲,要在Android中更改数据库架构,必须调用SQLHelper类的onUpgrade()方法,并且必须在此执行对数据库架构的所有更改。增加数据库版本号会告诉系统数据库模式(例如,新表列)已更改,这将触发SQLHelper类的OnUpgrade()方法。

这里是有关如何使用数据库版本号的示例。假设我们当前的数据库版本是4。

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion > 1) {
         // the code to upgrade from our initial database to v2
    }
    if (oldVersion > 2) {
         // the code to upgrade from v2 to v3
    }
    if (oldVersion > 3) {
         // the code to upgrade from v3 to v4
    }
}

因此,现在,如果用户的当前数据库版本低于3,则重新安装该应用程序时,将调用onUpgrade()。并且,如果拥有用户数据库的旧数据库版本,我们可以在不使用任何数据库的情况下充分迁移到新的数据库架构数据。

答案 3 :(得分:0)

例如,如果您已向表中添加了列,则必须始终更改版本。

但是,在某些情况下(仅测试新功能或如果您不关心丢失旧数据),您还可以强制onUpgrade()删除所有内容并重新创建它,并重新调用onCreate()。

但真正的意义是:

   @Override
    public void onUpgrade( SQLiteDatabase database, int oldVersion, int newVersion ) 
    {
      if (oldVersion<2)
         {
         database.execSQL(myQueryAlignment)
         }
    }

可以模拟更新(例如检查脚本是否有效) 使用ADV,删除模拟器上的数据库,插入旧数据库,然后 重新安装APK ...这样(如果新apk中增加了数据库版本),它将以“人工”方式运行onUpdate()。