Как обновить базу данных Android sql без сбоя приложения - PullRequest
0 голосов
/ 06 ноября 2018

Моя база данных в настоящее время имеет версию 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 голосов
/ 06 ноября 2018

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, который обрабатывает простые взаимодействия с БД open / close и сценарии, такие как:

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 инициализирует базу данных, которая вызывает классы дочерней таблицы для их сценариев создания или обновления. ПРИМЕЧАНИЕ * Это очень старый код, я больше не использую. Возможно, вам удастся отойти от 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, который вы выполняете, чтобы найти недостатки, если вы решите продолжить устранение неполадок в вашей текущей архитектуре.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...