web-dev-qa-db-fra.com

Meilleures pratiques pour travailler avec plusieurs tables

J'utilise une base de données avec plusieurs tables dans mon application. J'ai un analyseur XML qui doit écrire des données dans deux tables lors de l'analyse. J'ai créé deux adaptateurs de base de données pour les deux tables, mais maintenant j'ai un problème. Quand je travaille avec une seule table, c'est facile:

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(); 

Puisqu'il s'agit d'un analyseur SAX, à mon avis (peut-être que je me trompe), ce sera encore mieux:

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();
}

Mais comment faire si j'ai besoin d'insérer des données dans la deuxième table? Par exemple, si j'ai le deuxième adaptateur, je pense que ce sera une mauvaise idée:

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

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

Avez-vous des réflexions sur la façon d'y parvenir?

30
Burjua

Mon adaptateur de base de données. Une instance est toujours stockée dans MyApplication qui hérite d'Application. Pensez juste à une deuxième table où j'ai défini la première ... actuellement ce n'est qu'une version courte, en réalité cet adaptateur gère 7 tables dans la base de données.

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);
        }
    }
}
15
WarrenFaith

J'ai réussi à créer une classe de base abstraite avec l'instruction nom/création de base de données et d'autres informations partagées, puis à l'étendre pour chaque table. De cette façon, je peux garder toutes mes méthodes CRUD séparées (ce que je préfère de beaucoup). Le seul inconvénient est que les instructions DATABASE_CREATE doivent résider dans la classe parente et doivent inclure toutes les tables, car de nouvelles tables ne peuvent pas être ajoutées par la suite, mais à mon avis, c'est un petit prix à payer pour garder le CRUD méthodes distinctes pour chaque table.

Cela a été assez simple, mais voici quelques notes:

  • L'instruction create dans la classe parente doit être décomposée pour chaque table, car db.execSQL ne peut pas exécuter plus d'une instruction.
  • J'ai changé tous les vars/méthodes privés en protégés, juste au cas où.
  • Si vous ajoutez des tables à une application existante (vous ne savez pas si cela est spécifique à l'émulateur), l'application doit être désinstallée puis réinstallée.

Voici le code de ma classe parent abstraite, qui était basé sur le didacticiel du bloc-notes. Les enfants étendent simplement cela, en appelant le constructeur du super (n'hésitez pas à l'utiliser):

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();
    }

}

Une explication un peu plus détaillée est disponible ici: http://pheide.com/page/11/tab/24#post1

31
phoxicle

la solution de phoxicle est un excellent point de départ, mais selon Kevin Galligan notes sur la sérialisation SQLite d'Android , cette implémentation n'est pas sécurisée pour les threads et échouera silencieusement lorsque plusieurs connexions à la base de données (par exemple à partir de threads différents) essaient d'écrire le base de données:

Si vous essayez d'écrire dans la base de données à partir de connexions distinctes réelles en même temps, l'une échouera. Il n'attendra pas que le premier soit terminé, puis rédigera. Il n'écrira tout simplement pas votre changement. Pire, si vous n'appelez pas la bonne version d'insertion/mise à jour sur la SQLiteDatabase, vous n'aurez pas d'exception. Vous recevrez simplement un message dans votre LogCat, et ce sera tout.

Donc, plusieurs threads? Utilisez un assistant.


Voici une implémentation modifiée de l'adaptateur de base de données de phoxicle qui utilise une instance statique SQLiteOpenHelper et est donc limitée à une seule connexion à la base de données:

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);
        }
    }
}

Étendez DBBaseAdapter pour chaque table pour implémenter vos méthodes CRUD:

public class DBFooTable extends DBBaseAdapter {

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

    public void getBar() {

        SQLiteDatabase db = openDb();
        // ...
        closeDb();
}
10
mjama

Je suis peut-être un peu en retard mais j'ouvre toujours ma base de données, pas ma table. Donc, cela ne me fait aucun sens.

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

Faites-le plutôt.

    dataBase.getWritableDatabase();

alors si vous voulez mettre à jour, choisissez simplement la table:

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);
}

Et c'est tout. J'espère que cela peut aider d'autres personnes

1
1020rpz