我的数据库当前为版本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);)
#################################################################
答案 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,以查找缺陷。