如何在不使应用程序崩溃的情况下更新您的android sql数据库

时间:2018-11-06 12:58:52

标签: android database sqlite

我的数据库当前为版本1,但是我在应用商店上有一个应用,并且为我的新更新添加了一个列,并且我知道我必须更新数据库,否则当我发送我的应用进行更新时,它将崩溃其他用户的应用程序,如何在不崩溃的情况下安全地升级数据库,我尝试在手机上运行旧版本,并在增量后使用此代码运行新的升级,但它一直崩溃而没有错误代码。如何在不安全破坏应用商店上安装的其他用户的情况下升级数据库?

我的数据库类:

Error:

     Caused by: android.database.sqlite.SQLiteException: table items already exists (code 1): , while compiling: CREATE TABLE items (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, type TEXT NOT NULL, logo INTEGER NOT NULL DEFAULT 0, color INTEGER NOT NULL DEFAULT 0, created_date DATE NOT NULL DEFAULT CURRENT_TIMESTAMP);
        #################################################################
        Error Code : 1 (SQLITE_ERROR)
        Caused By : SQL(query) error or missing database.
            (table items already exists (code 1): , while compiling: CREATE TABLE items (_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, type TEXT NOT NULL, logo INTEGER NOT NULL DEFAULT 0, color INTEGER NOT NULL DEFAULT 0, created_date DATE NOT NULL DEFAULT CURRENT_TIMESTAMP);)
        #################################################################

1 个答案:

答案 0 :(得分:1)

仅当您的数据库版本已增加时才调用onUpgrade。因此,您无需检查它是否较新。

因此,升级数据库时有两个选择。

1)onUpgrade调用DROP TABLE和CREATE TABLE重新启动

2)您在onUpgrade回调中运行更改脚本。如果您需要添加列或移动数据,请进行适当处理。换句话说,如果添加一个不可为空的字段,则需要更新所有其他字段。或者,如果您打算四处移动数据,则可以查询数据,形成对象,然后插入新结构中。

但是从外观上看,您只是在做一个简单的列添加。因此,只要增加静态数据库编号,就可以了。

更新以进一步

好吧,让我们谈谈以非会议室方式管理数据库的问题。现在,我更喜欢使用Android的Room,但是您现在不必学习新的知识,我将向您展示如何管理数据库以供您使用。

首先,我创建一个界面,例如:

 public interface IA35Table {

        /*//////////////////////////////////////////////////////////////////////////////////////////////
        //EXTERNAL METHODS
        *///////////////////////////////////////////////////////////////////////////////////////////////
        /**
         * @return the SQL script to crate the table.
         */
        String getCreateTableScript();
        /**
         * @return the SQL script to upgrade the table.
         */
        String getUpgradeTableScript();

    }

然后我为每个表创建一个类。这是一个示例表:

 public class ContactFavoritesTable implements IA35Table {

    /*//////////////////////////////////////////////////////////
    // MEMBERS
    *///////////////////////////////////////////////////////////
    private static final String TAG = Globals.SEARCH_STRING + ContactFavoritesTable.class.getSimpleName();
    public static final String TABLE_NAME = "contactFavorites";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_CONTACT_FAVORITE_ID = "contactId";


    /*//////////////////////////////////////////////////////////
    // DATABASE METHODS
    *///////////////////////////////////////////////////////////
    @Override
    public String getCreateTableScript() {
        try{
            StringBuilder schema = new StringBuilder();

            schema.append("CREATE TABLE " + TABLE_NAME);
            schema.append(" ( ");
            schema.append(COLUMN_ID + " INTEGER PRIMARY KEY");
            schema.append(", ");
            schema.append(COLUMN_CONTACT_FAVORITE_ID + " INTEGER");
            schema.append(")");

            Log.d(TAG, "Creating Contact Favorites Table. Query: " + schema.toString());

            return schema.toString();
        }catch (Exception e){
            Log.e(TAG, "Failed to Create Favorites Table because: " + e.getMessage());
            return "";
        }
    }
    @Override
    public String getUpgradeTableScript() {
        try{
            String query = "DROP TABLE IF EXISTS " + TABLE_NAME;
            A35Log.d(TAG, "Dropping " + TABLE_NAME + " Table. Query: " + query);
            return query;
        }catch (Exception e){
            A35Log.e(TAG, "Failed to Drop Existing Database");
            return "";
        }
    }
}

接下来,我制作一个通用的DBHelper类,该类处理打开/关闭和脚本之类的简单DB交互:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import android.util.Log;

import com.a35.interfaces.IA35Table;

import java.util.List;
import java.util.StringTokenizer;

/**
 * Created by App Studio 35 on 5/25/16.
 * <p>
 * Database helper class for Data Definition Language (DDL) and Data Manipulation Language(DML).
 */
public final class A35DBHelper extends SQLiteOpenHelper {

    /*///////////////////////////////////////////////////////////////////////////////////////////////
    //MEMBERS
    *////////////////////////////////////////////////////////////////////////////////////////////////
    private static final String TAG = A35DBHelper.class.getSimpleName();
    private static int sDatabaseVersion;
    private static String sDatabaseName = null;
    private static List<Class<? extends IA35Table>> sTables;


    /*//////////////////////////////////////////////////////////////////////////////////////////////
    // CONSTRUCTOR AND INIT
    *///////////////////////////////////////////////////////////////////////////////////////////////
    private A35DBHelper(Context context) {
        // Use the application context, which will ensure that you don't accidentally leak an Activity's context.
        super(context.getApplicationContext(), sDatabaseName, null, sDatabaseVersion);
    }
    /**
     * Used to set database name and version and supply classes that follow our design implementation
     *
     * @param databaseName the database name.
     * @param databaseVersion the database version.
     */
    public static void initialize(String databaseName, int databaseVersion, List<Class<? extends IA35Table>> tables) {
        sDatabaseName = databaseName;
        sDatabaseVersion = databaseVersion;
        sTables = tables;
    }


    /*///////////////////////////////////////////////////////////////////////////////////////////////
    //OVERRIDE METHODS
    *////////////////////////////////////////////////////////////////////////////////////////////////
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        for(Class<? extends IA35Table> table : sTables){
            try {
                sqLiteDatabase.execSQL(table.newInstance().getCreateTableScript());
            } catch (InstantiationException e) {
                Log.e(TAG, "onCreate: Caught when instantiating the table.", e);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "onCreate: Caught when accessing the table.", e);
            }
        }
    }
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        for (Class<? extends IA35Table> table : sTables) {
            try {
                String updateTableScript = table.newInstance().getUpgradeTableScript();
                executeMultipleQueryIfAvailable(sqLiteDatabase, updateTableScript, false);
            } catch (InstantiationException e) {
                Log.e(TAG, "onUpgrade: Caught when instantiating the table.", e);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "onUpgrade: Caught when accessing the table.", e);
            }
        }
    }


    /*//////////////////////////////////////////////////////////////////////////////////////////////
    //PUBLIC METHODS
    *///////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Use with Caution, this gives actual database and should be followed up with closeDatabase when complete
     *
     * @param context the Context.
     * @return the writable instance of SQLiteDatabase.
     */
    public static SQLiteDatabase openDatabase(Context context) throws IllegalArgumentException{
        return getA35DBHelper(context).getWritableDatabase();
    }
    /**
     * Close Database if used openDatabase
     *
     * @param db the instance of SQLiteDatabase.
     */
    public static void closeDatabase(SQLiteDatabase db){
        try {
            db.close();
        } catch (Exception e) {
            Log.e(TAG, "Unable to close: " + db, e);
        }
    }


    /*//////////////////////////////////////////////////////////////////////////////////////////////
    //PRIVATE METHODS
    *///////////////////////////////////////////////////////////////////////////////////////////////
    private static A35DBHelper getA35DBHelper(Context context) throws IllegalArgumentException{
        //Internal method to make sure if they did not call initialize we can tell them what went wrong
        try{
            return new A35DBHelper(context);
        }catch (IllegalArgumentException ex){
            throw new IllegalArgumentException("MUST call A35DBHelper.initialize before using the helper methods");
        }
    }
    /**
     * To execute the multiple query sequentially.
     * @param sqLiteDatabase the instance of SQLiteDatabase.
     * @param sqlScript the sql script.
     * @param isRequiredToCloseSQLiteDatabase "true" if user like to close the SQLiteDatabase, Otherwise "false". (Note: We are calling this method for {@link A35DBHelper#onUpgrade(SQLiteDatabase, int, int)}, where we don't need to close the database instance.
     */
    private static void executeMultipleQueryIfAvailable(SQLiteDatabase sqLiteDatabase, String sqlScript, boolean isRequiredToCloseSQLiteDatabase){
        if(!TextUtils.isEmpty(sqlScript)) {
            StringTokenizer queries = new StringTokenizer(sqlScript, SQLSyntaxHelper.MULTIPLE_QUERY_SEPERATOR);
            sqLiteDatabase.beginTransaction();
            try {
                while (queries.hasMoreTokens()) {
                    sqLiteDatabase.execSQL(queries.nextToken());
                }
                sqLiteDatabase.setTransactionSuccessful();
            } finally {
                sqLiteDatabase.endTransaction();
            }

            if(isRequiredToCloseSQLiteDatabase) {
                try {
                    sqLiteDatabase.close();
                 } catch (Exception e) {
                     Log.e(TAG, "Unable to close: " + sqLiteDatabase, e);
                 }
            }
        }
    }
}

接下来在您的Application类中,我添加了以下两个方法来将其全部初始化:

    private void setupDatabase(){
        A35DBHelper.initialize(DATABASE_NAME, DATABASE_VERSION, getDBTables());
    }
    private ArrayList<Class<? extends IA35Table>> getDBTables(){
        ArrayList<Class<? extends IA35Table>> tables = new ArrayList<>();
        tables.add(ContactFavoritesTable.class);
        tables.add(OtherTables.class);
        return tables;
    }

因此,现在您可以看到,Application类将初始化Database,该数据库将调用子表类为其创建脚本或升级脚本。注意*这是非常旧的代码,我不再使用。您也许可以离开表类的newInstance并使用static或将其与伴随对象一起移至kotlin。

最后,我创建了一个数据源以与之交互。

免责声明,该类与上面的示例不符。

这是因为除此示例外,我找不到适合您的Java示例。我现在只有科特林。但是,唯一的区别是,它使用contentProvider来获取游标(因为它是一个公开的共享数据库。但是它仍然可以很好地理解整个体系结构。

因此,在任何可以看到游标解析的地方,将其替换为获取A35DatabaseHelper.openDatabase并执行查询脚本以返回游标,最后将其始终添加到始终A35DatabaseHelper.close()。其余的将排队。

public class ContactFavoritesDataSource {

/*//////////////////////////////////////////////////////////
// MEMBERS
*///////////////////////////////////////////////////////////
private static final String TAG = Globals.SEARCH_STRING + ContactFavoritesDataSource.class.getSimpleName();
public static final String[] CONTACT_FAVORITES_PROJECTION = {
        ContactFavoritesTable.COLUMN_ID,
        ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID
};


/*//////////////////////////////////////////////////////////
// CRUD OPERATIONS
*///////////////////////////////////////////////////////////
public static ArrayList<A35ContactFavorite> getAllContactFavorites(Context context) {
    ArrayList<A35ContactFavorite> favoritesList = new ArrayList<A35ContactFavorite>();

    try{
        Cursor cursor = context.getContentResolver().query(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI, CONTACT_FAVORITES_PROJECTION, null, null, null);
        if(cursor != null){
            if(cursor.moveToFirst()){
                do {
                    A35ContactFavorite contactFavorite = new A35ContactFavorite();

                    contactFavorite.setId(cursor.getInt(cursor.getColumnIndex(ContactFavoritesTable.COLUMN_ID)));
                    contactFavorite.setContactId(cursor.getString(cursor.getColumnIndex(ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID)));

                    favoritesList.add(contactFavorite);

                } while (cursor.moveToNext());

            }

            cursor.close();
        }

    }catch (Exception e){
        A35Log.e(TAG, e.getMessage());

    }

    return favoritesList;

}
public static A35ContactFavorite getContactFavoriteById(Context context, String contactId){
    A35ContactFavorite foundFav = null;

    try{
        Uri uriForId = Uri.parse(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI + "/" + contactId);
        Cursor cursor = context.getContentResolver().query(uriForId, CONTACT_FAVORITES_PROJECTION, null, null, null);
        if(cursor != null){
            if(cursor.moveToFirst()){
                do {
                    foundFav = new A35ContactFavorite();

                    foundFav.setId(cursor.getInt(cursor.getColumnIndex(ContactFavoritesTable.COLUMN_ID)));
                    foundFav.setContactId(cursor.getString(cursor.getColumnIndex(ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID)));

                } while (cursor.moveToNext());

            }

            cursor.close();
        }

    }catch (Exception e){
        A35Log.e(TAG, e.getMessage());

    }

    return foundFav;
}
public static boolean getContactIsFavorite(Context context, A35Contact contact){
    boolean exists = false;

    try{
        Uri uriForId = Uri.parse(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI + "/" + contact.getId());
        Cursor cursor = context.getContentResolver().query(uriForId, CONTACT_FAVORITES_PROJECTION, null, null, null);
        if(cursor != null){
            if(cursor.moveToFirst()){
                do {
                    String foundId = cursor.getString(cursor.getColumnIndex(ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID));

                    if(contact.getId().equals(foundId)){
                        exists = true;
                    }

                } while (cursor.moveToNext());

            }

            cursor.close();
        }

    }catch (Exception e){
        A35Log.e(TAG, e.getMessage());

    }

    return exists;
}
public static long insertContactFavorite(Context context, A35ContactFavorite contactFavorite){
    long contactFavId = -1;

    try{
        ContentValues values = new ContentValues();
        values.put(ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID, contactFavorite.getContactId());
        Uri insertUri = context.getContentResolver().insert(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI, values);
        contactFavId = Integer.parseInt(insertUri.getLastPathSegment());
        contactFavorite.setId(contactFavId);

    }catch (Exception e){
        A35Log.e(TAG, e.getMessage());

    }

    return contactFavId;

}
public static int insertMultipleContactsToFavorites(Context context, ArrayList<A35Contact> contacts){
    int count = 0;

    try {
        for (A35Contact contact : contacts) {
            ContentValues values = new ContentValues();
            values.put(ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID, contact.getId());
            context.getContentResolver().insert(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI, values);
            count++;

        }

    } catch (Exception e) {
        A35Log.e(TAG, e.getMessage());
        count = -1;

    }

    return count;
}
public static int insertMultipleContactFavorites(Context context, ArrayList<A35ContactFavorite> contactFavorites){
    int count = 0;

    try {
        for (A35ContactFavorite contactFav : contactFavorites) {
            ContentValues values = new ContentValues();
            values.put(ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID, contactFav.getContactId());
            context.getContentResolver().insert(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI, values);
            count++;

        }

    } catch (Exception e) {
        A35Log.e(TAG, e.getMessage());
        count = -1;

    }

    return count;
}
public static int removeContactFavorite(Context context, A35ContactFavorite contactFavorite) {
    int count = 0;

    try{
        Uri uriForId = Uri.parse(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI + "/" + String.valueOf(contactFavorite.getId()));
        count = context.getContentResolver().delete(uriForId, null, null);

    }catch (Exception e){
        A35Log.e(TAG, e.getMessage());

    }

    return count;

}
public static int removeContactFavorite(Context context, long favId) {
    int count = 0;

    try{
        Uri uriForId = Uri.parse(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI + "/" + String.valueOf(favId));
        count = context.getContentResolver().delete(uriForId, null, null);

    }catch (Exception e){
        A35Log.e(TAG, e.getMessage());

    }

    return count;

}
public static int removeMultipleContactsFromFavorites(Context context, ArrayList<A35Contact> contacts){
    int count = 0;

    try {
        for (A35Contact contact : contacts) {
            String selection = ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID + SQLSyntaxHelper.IS_EQUAL_TO + contact.getId();

            Uri uriForId = Uri.parse(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI + "/");
            count += context.getContentResolver().delete(uriForId, selection, null);

        }

    } catch (Exception e) {
        A35Log.e(TAG, e.getMessage());
        count = -1;

    }

    return count;
}
public static int removeMultipleContactFavorites(Context context, ArrayList<A35ContactFavorite> contactFavorites){
    int count = 0;

    try {
        for (A35ContactFavorite contactFav : contactFavorites) {
            String selection = ContactFavoritesTable.COLUMN_CONTACT_FAVORITE_ID + SQLSyntaxHelper.IS_EQUAL_TO + contactFav.getContactId();

            Uri uriForId = Uri.parse(ThisWayContentProvider.CONTACT_FAVORITES_CONTENT_URI + "/");
            count += context.getContentResolver().delete(uriForId, selection, null);

        }

    } catch (Exception e) {
        A35Log.e(TAG, e.getMessage());
        count = -1;

    }

    return count;
}

}

我剩下的最后一件事是:

考虑使用ROOM,它的代码更少,易于使用且非常干净的数据库交互。然后,您不必在每个项目上都花些时间。

有一个学习曲线,但是值得一游。

https://developer.android.com/training/data-storage/room/

希望这会有所帮助。

您的错误暗示您正在尝试创建已经存在的东西。如果您选择继续对当前体系结构进行故障排除,则需要设置断点来检查正在执行的sql,以查找缺陷。