Лучшие практики для работы с несколькими таблицами - PullRequest
30 голосов
/ 10 сентября 2010

Я использую базу данных с несколькими таблицами в моем приложении.У меня есть синтаксический анализатор XML, который должен записывать данные в две таблицы при разборе.Я создал два адаптера базы данных для обеих таблиц, но теперь у меня проблема.Когда я работаю с одним столом, это легко:

FirstDBAdapter firstTable = new FirstDBAdapter(mycontext);
firstTable.open(); // open and close it every time I need to insert something
                   // may be hundreds of times while parsing
                   // it opens not a table but whole DB     
firstTable.insertItem(Item);        
firstTable.close(); 

Поскольку это парсер SAX, по моему мнению (возможно, я ошибаюсь), это будет еще лучше:

FirstDBAdapter firstTable = new FirstDBAdapter(mycontext);

@Override
public void startDocument() throws SAXException 
{
    firstTable.open(); // open and close only once
}

...
firstTable.insertItem(Item);
...

@Override
public void endDocument() throws SAXException 
{
    firstTable.close();
}

Но как мне это сделать, если мне нужно вставить данные во вторую таблицу?Например, если у меня есть второй адаптер, который, я думаю, будет плохой идеей:

FirstDBAdapter firstTable = new FirstDBAdapter(mycontext);
SecondDBAdapter secondTable = new SecondDBAdapter(mycontext);

@Override
public void startDocument() throws SAXException 
{
    firstTable.open();
    secondTable.open(); 
}

Есть мысли о том, как этого добиться?

Ответы [ 4 ]

31 голосов
/ 03 октября 2010

Мне удалось создать абстрактный базовый класс с оператором name / create базы данных и другой общей информацией, а затем расширить его для каждой таблицы. Таким образом, я могу хранить все свои методы CRUD отдельно (что я предпочитаю). Единственным недостатком является то, что оператор (ы) DATABASE_CREATE должен находиться в родительском классе и должен включать все таблицы, потому что новые таблицы не могут быть добавлены впоследствии, но, по моему мнению, это небольшая цена, которую нужно платить, чтобы сохранить CRUD методы для каждой таблицы раздельные.

Сделать это было довольно просто, но вот некоторые замечания:

  • Оператор create в родительском классе должен разбиваться для каждой таблицы, поскольку db.execSQL не может выполнить более одного оператора.
  • Я на всякий случай изменил все частные переменные / методы на защищенные.
  • Если вы добавляете таблицы в существующее приложение (не уверены, относится ли оно к эмулятору), приложение необходимо удалить, а затем переустановить.

Вот код моего абстрактного родительского класса, основанного на учебном пособии «Блокнот». Дети просто расширяют это, вызывая конструктор супер (не стесняйтесь использовать это):

package com.pheide.trainose;

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

public abstract class AbstractDbAdapter {

    protected static final String TAG = "TrainOseDbAdapter";
    protected DatabaseHelper mDbHelper;
    protected SQLiteDatabase mDb;

    protected static final String TABLE_CREATE_ROUTES =
        "create table routes (_id integer primary key autoincrement, "
        + "source text not null, destination text not null);";
    protected static final String TABLE_CREATE_TIMETABLES =    
        "create table timetables (_id integer primary key autoincrement, "
        + "route_id integer, depart text not null, arrive text not null, "
        + "train text not null);";

    protected static final String DATABASE_NAME = "data";
    protected static final int DATABASE_VERSION = 2;

    protected final Context mCtx;

    protected static class DatabaseHelper extends SQLiteOpenHelper {

        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(TABLE_CREATE_ROUTES);
            db.execSQL(TABLE_CREATE_TIMETABLES);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                    + newVersion + ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS routes");
            onCreate(db);
        }
    }

    public AbstractDbAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    public AbstractDbAdapter open() throws SQLException {
        mDbHelper = new DatabaseHelper(mCtx);
        mDb = mDbHelper.getWritableDatabase();
        return this;
    }

    public void close() {
        mDbHelper.close();
    }

}

Чуть более подробное объяснение доступно здесь: http://pheide.com/page/11/tab/24#post13

15 голосов
/ 10 сентября 2010

Мой адаптер базы данных.Экземпляр всегда хранится в MyApplication, который наследуется от Application.Подумайте о второй таблице, в которой я определил первую ... в настоящее время это всего лишь короткая версия, в действительности этот адаптер обрабатывает 7 таблиц в базе данных.

public class MyDbAdapter {
    private static final String LOG_TAG = MyDbAdapter.class.getSimpleName();

    private SQLiteDatabase mDb;
    private static MyDatabaseManager mDbManager;

    public MyDbAdapter() {
        mDbManager = new MyDatabaseManager(MyApplication.getApplication());
        mDb = mDbManager.getWritableDatabase();
    }

    public static final class GameColumns implements BaseColumns {
        public static final String TABLE = "game";
        public static final String IMEI = "imei";
        public static final String LAST_UPDATE = "lastupdate";
        public static final String NICKNAME = "nickname";
    }

    public String getImei() {
        checkDbState();
        String retValue = "";
        Cursor c = mDb.rawQuery("SELECT imei FROM " + GameColumns.TABLE, null);
        if (c.moveToFirst()) {
            retValue = c.getString(c.getColumnIndex(GameColumns.IMEI));
        }
        c.close();
        return retValue;
    }

    public void setImei(String imei) {
        checkDbState();
        ContentValues cv = new ContentValues();
        cv.put(GameColumns.IMEI, imei);
        mDb.update(GameColumns.TABLE, cv, null, null);
    }

    public boolean isOpen() {
        return mDb != null && mDb.isOpen();
    }

    public void open() {
        mDbManager = new MyDatabaseManager(MyApplication.getApplication());
        if (!isOpen()) {
            mDb = mDbManager.getWritableDatabase();
        }
    }

    public void close() {
        if (isOpen()) {
            mDb.close();
            mDb = null;
            if (mDbManager != null) {
                mDbManager.close();
                mDbManager = null;
            }
        }
    }

    private void checkDbState() {
        if (mDb == null || !mDb.isOpen()) {
            throw new IllegalStateException("The database has not been opened");
        }
    }

    private static class MyDatabaseManager extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "dbname";
        private static final int DATABASE_VERSION = 7;

        private MyDatabaseManager(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            createGameTable(db);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + "!");
        }

        private void dropDatabase(SQLiteDatabase db) {
            db.execSQL("DROP TABLE IF EXISTS " + GameColumns.TABLE);
        }

        private void createGameTable(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE " + GameColumns.TABLE + " ("
                    + GameColumns._ID + " INTEGER PRIMARY KEY,"
                    + GameColumns.IMEI + " TEXT,"
                    + GameColumns.LAST_UPDATE + " TEXT,"
                    + GameColumns.NICKNAME + " TEXT);");
            ContentValues cv = new ContentValues();
            cv.put(GameColumns.IMEI, "123456789012345");
            cv.put(GameColumns.LAST_UPDATE, 0);
            cv.put(GameColumns.NICKNAME, (String) null);
            db.insert(GameColumns.TABLE, null, cv);
        }
    }
}
10 голосов
/ 23 марта 2013

Решение Phoxicle - отличная отправная точка, но согласно примечаниям Кевина Галлигана о сериализации Android в SQLite , эта реализация не является поточно-ориентированной и потерпит неудачу, когда несколько соединений с базой данных (например, из разных потоков) пытаются написать базу данных:

Если вы попытаетесь одновременно выполнить запись в базу данных из разных соединений, произойдет сбой. Это не будет ждать, пока первое будет сделано, а затем напишите. Это просто не будет писать ваши изменения. Хуже того, если вы не вызовете нужную версию вставки / обновления в базе данных SQLiteD, вы не получите исключение. Вы просто получите сообщение в LogCat, и это будет оно.

Итак, несколько потоков? Используйте один помощник.


Вот модифицированная реализация адаптера базы данных phoxicle, которая использует статический экземпляр SQLiteOpenHelper и, следовательно, ограничена одним подключением к базе данных:

public class DBBaseAdapter {

    private static final String TAG = "DBBaseAdapter";

    protected static final String DATABASE_NAME = "db.sqlite";
    protected static final int DATABASE_VERSION = 1;

    protected Context mContext;
    protected static DatabaseHelper mDbHelper;

    private static final String TABLE_CREATE_FOO = 
        "create table foo (_id integer primary key autoincrement, " +
        "bar text not null)");

    public DBBaseAdapter(Context context) {
        mContext = context.getApplicationContext();
    }

    public SQLiteDatabase openDb() {
        if (mDbHelper == null) {
            mDbHelper = new DatabaseHelper(mContext);
        }
        return mDbHelper.getWritableDatabase();
    }

    public void closeDb() {
        mDbHelper.close();
    }

    protected static class DatabaseHelper extends SQLiteOpenHelper {

        public DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(TABLE_CREATE_FOO);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to " +
                newVersion + ", which will destroy all old data");
            db.execSQL("DROP TABLE IF EXISTS routes");
            onCreate(db);
        }
    }
}

Расширьте DBBaseAdapter для каждой таблицы, чтобы реализовать ваши методы CRUD:

public class DBFooTable extends DBBaseAdapter {

    public DBFooTable(Context context) {
        super(context);
    }

    public void getBar() {

        SQLiteDatabase db = openDb();
        // ...
        closeDb();
}
1 голос
/ 15 апреля 2015

Возможно, я немного опоздал, но я всегда открываю свою базу данных, а не свою таблицу.Так что это не имеет смысла.

    firstTable.open();
    secondTable.open(); 

Скорее сделайте это.

    dataBase.getWritableDatabase();

, тогда, если вы хотите обновить juste, выберите таблицу:

public int updateTotal (int id, Jours jour){
    ContentValues values = new ContentValues();

    values.put(COL_TOTAL,Total );

    //update the table you want
    return bdd.update(TABLE_NAME, values, COL_JOUR + " = " + id, null);
}

И это все.Надеюсь, что это может помочь другим людям

...