2010-10-31 11 views
60

estaba leyendo el tutorial Bloc de notas Android SQLite que hacía referencia a la creación de una clase adaptador DB para crear y acceder a una tabla DB. Al tratar con una base de datos SQLite de múltiples tablas, ¿es una buena práctica crear una clase de adaptador diferente para cada tabla o crear una única clase de adaptador de base de datos para toda la aplicación de Android?¿Adaptador (s) DB de tabla múltiple en Android?

Mi aplicación utiliza varias tablas y esperaba no tener que tener una sola clase de adaptador masiva. el problema, sin embargo, es que tengo una subclase anidada de SQLiteOpenHelper por el ejemplo de NotePad dentro de cada adaptador. Cuando se accede a la primera tabla, todo funciona bien. Cuando intento acceder al segundo tble (de una actividad diferente) mi aplicación se cuelga.

Al principio, pensé que el accidente fue causado por un problema de versiones, pero ambos adaptadores ahora tener la misma versión de base de datos y todavía está chocando.

He aquí un ejemplo de uno de los adaptadores de base de datos para la tabla. Los otros adaptadores siguen el mismo formato con implementaciones variables.

public class InfoDBAdapter { 
    public static final String ROW_ID = "_id"; 
    public static final String NAME = "name"; 

    private static final String TAG = "InfoDbAdapter"; 
    private static final String DATABASE_NAME = "myappdb"; 
    private static final String DATABASE_TABLE = "usersinfo"; 
    private static final int DATABASE_VERSION = 1; 


    private static final String DATABASE_CREATE = "create table usersinfo (_id integer primary key autoincrement, " 
      + NAME 
      + " TEXT," + ");"; 

    private DatabaseHelper mDbHelper; 
    private SQLiteDatabase mDb; 

    private final Context mCtx; 

    private static class DatabaseHelper extends SQLiteOpenHelper { 

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

     @Override 
     public void onCreate(SQLiteDatabase db) { 

      db.execSQL(DATABASE_CREATE); 
     } 

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


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


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

    /** 
    * close return type: void 
    */ 
    public void close() { 
     this.mDbHelper.close(); 
    } 


    public long createUser(String name) { 
     ContentValues initialValues = new ContentValues(); 
     initialValues.put(NAME, name); 
     return this.mDb.insert(DATABASE_TABLE, null, initialValues); 
    } 


    public boolean deleteUser(long rowId) { 

     return this.mDb.delete(DATABASE_TABLE, ROW_ID + "=" + rowId, null) > 0; //$NON-NLS-1$ 
    } 


    public Cursor fetchAllUsers() { 

     return this.mDb.query(DATABASE_TABLE, new String[] { ROW_ID, 
       NAME}, null, null, null, null, null); 
    } 


    public Cursor fetchUser(long rowId) throws SQLException { 

     Cursor mCursor = 

     this.mDb.query(true, DATABASE_TABLE, new String[] { ROW_ID, NAME}, ROW_ID + "=" + rowId, null, //$NON-NLS-1$ 
       null, null, null, null); 
     if (mCursor != null) { 
      mCursor.moveToFirst(); 
     } 
     return mCursor; 

    } 


    public boolean updateUser(long rowId, String name) { 
     ContentValues args = new ContentValues(); 
     args.put(NAME, name); 
     return this.mDb 
       .update(DATABASE_TABLE, args, ROW_ID + "=" + rowId, null) > 0; //$NON-NLS-1$ 
    } 
} 

Cuando se accede al primer adaptador, en este caso usersinfo, todo funciona como se esperaba. Digamos que tengo otro adaptador para información de amigos que sigue la misma estructura que la anterior, cuando se accede por una actividad diferente, me parece que la subclase anidada de SQLiteOpenHelper intentará crear la base de datos nuevamente. Obviamente, algo está mal porque en ese escenario, mi aplicación falla.

Así es la práctica común dentro de Android para crear un único adaptador db mamut en lugar de adaptadores individuales por mesa?

+2

+1: he estado tratando de hacerme a la idea de esto también. Tener solo una tabla o dos, no hay problema para poner todo dentro de la misma clase de adaptador DB. Sin embargo, si el DB crece con muchas tablas, se volverá complicado. Entonces realmente me interesa si alguien puede señalar algunas mejores prácticas en esta área. – Nailuj

+0

Aquí está la misma pregunta pero con respuestas reales: http://stackoverflow.com/questions/3684678 – jcmcbeth

Respuesta

79

Aquí está la solución que finalmente terminó la implementación. Es una mezcla de información obtenida en los libros de Commons, y algunas cosas en la web que me gustaría marcar porque quiero dar crédito:

Para cada tipo de datos que necesito extraer de la base de datos , Creo una clase "adaptadora" (no subclasificada de nada). Estas clases de adaptadores contienen todos los métodos necesarios para acceder a la base de datos para esa información. Por ejemplo, si tuviera tres tablas en mi db:

  1. Coches
  2. Barcos
  3. Motocicletas

que tendría tres adaptadores que sería similar a la siguiente (sólo soy poniendo en uno como un demo, pero la idea es la misma para cada uno):

public class CarsDBAdapter { 
    public static final String ROW_ID = "_id"; 
    public static final String NAME = "name"; 
    public static final String MODEL = "model"; 
    public static final String YEAR = "year"; 

    private static final String DATABASE_TABLE = "cars"; 

    private DatabaseHelper mDbHelper; 
    private SQLiteDatabase mDb; 

    private final Context mCtx; 

    private static class DatabaseHelper extends SQLiteOpenHelper { 

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

     @Override 
     public void onCreate(SQLiteDatabase db) { 
     } 

     @Override 
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 
     } 
    } 

    /** 
    * Constructor - takes the context to allow the database to be 
    * opened/created 
    * 
    * @param ctx 
    *   the Context within which to work 
    */ 
    public CarsDBAdapter(Context ctx) { 
     this.mCtx = ctx; 
    } 

    /** 
    * Open the cars database. If it cannot be opened, try to create a new 
    * instance of the database. If it cannot be created, throw an exception to 
    * signal the failure 
    * 
    * @return this (self reference, allowing this to be chained in an 
    *   initialization call) 
    * @throws SQLException 
    *    if the database could be neither opened or created 
    */ 
    public CarsDBAdapter open() throws SQLException { 
     this.mDbHelper = new DatabaseHelper(this.mCtx); 
     this.mDb = this.mDbHelper.getWritableDatabase(); 
     return this; 
    } 

    /** 
    * close return type: void 
    */ 
    public void close() { 
     this.mDbHelper.close(); 
    } 

    /** 
    * Create a new car. If the car is successfully created return the new 
    * rowId for that car, otherwise return a -1 to indicate failure. 
    * 
    * @param name 
    * @param model 
    * @param year 
    * @return rowId or -1 if failed 
    */ 
    public long createCar(String name, String model, String year){ 
     ContentValues initialValues = new ContentValues(); 
     initialValues.put(NAME, name); 
     initialValues.put(MODEL, model); 
     initialValues.put(YEAR, year); 
     return this.mDb.insert(DATABASE_TABLE, null, initialValues); 
    } 

    /** 
    * Delete the car with the given rowId 
    * 
    * @param rowId 
    * @return true if deleted, false otherwise 
    */ 
    public boolean deleteCar(long rowId) { 

     return this.mDb.delete(DATABASE_TABLE, ROW_ID + "=" + rowId, null) > 0; //$NON-NLS-1$ 
    } 

    /** 
    * Return a Cursor over the list of all cars in the database 
    * 
    * @return Cursor over all cars 
    */ 
    public Cursor getAllCars() { 

     return this.mDb.query(DATABASE_TABLE, new String[] { ROW_ID, 
       NAME, MODEL, YEAR }, null, null, null, null, null); 
    } 

    /** 
    * Return a Cursor positioned at the car that matches the given rowId 
    * @param rowId 
    * @return Cursor positioned to matching car, if found 
    * @throws SQLException if car could not be found/retrieved 
    */ 
    public Cursor getCar(long rowId) throws SQLException { 

     Cursor mCursor = 

     this.mDb.query(true, DATABASE_TABLE, new String[] { ROW_ID, NAME, 
       MODEL, YEAR}, ROW_ID + "=" + rowId, null, null, null, null, null); 
     if (mCursor != null) { 
      mCursor.moveToFirst(); 
     } 
     return mCursor; 
    } 

    /** 
    * Update the car. 
    * 
    * @param rowId 
    * @param name 
    * @param model 
    * @param year 
    * @return true if the note was successfully updated, false otherwise 
    */ 
    public boolean updateCar(long rowId, String name, String model, 
      String year){ 
     ContentValues args = new ContentValues(); 
     args.put(NAME, name); 
     args.put(MODEL, model); 
     args.put(YEAR, year); 

     return this.mDb.update(DATABASE_TABLE, args, ROW_ID + "=" + rowId, null) >0; 
    } 

} 

Así que si usted imaginar que tengo una de estas clases "adaptadores" para cada mesa.

Cuando se inicia la pantalla de mi salpicaduras aplicación, utilizo la técnica presentada Android For Beginners: Creating multiple SQLite Tables for Android

Así que mi dbAdapter principal (que es responsable de crear todas mis tablas en una sola db) tiene el siguiente aspecto:

public class DBAdapter { 

    public static final String DATABASE_NAME = "stuffIOwn"; //$NON-NLS-1$ 

    public static final int DATABASE_VERSION = 1; 

    private static final String CREATE_TABLE_CARS = 
     "create table cars (_id integer primary key autoincrement, " //$NON-NLS-1$ 
    + CarsDBAdapter.NAME+ " TEXT," //$NON-NLS-1$ 
    + CarsDBAdapter.MODEL+ " TEXT," //$NON-NLS-1$ 
    + CarsDBAdapter.YEAR+ " TEXT" + ");"; //$NON-NLS-1$ //$NON-NLS-2$ 

    private static final String CREATE_TABLE_BOATS = "create table boats (_id integer primary key autoincrement, " //$NON-NLS-1$ 
    +BoatsDBAdapter.NAME+" TEXT," //$NON-NLS-1$ 
    +BoatsDBAdapter.MODEL+" TEXT," //$NON-NLS-1$ 
    +BoatsDBAdapter.YEAR+" TEXT"+ ");"; //$NON-NLS-1$ //$NON-NLS-2$ 

     private static final String CREATE_TABLE_CYCLES = "create table cycles (_id integer primary key autoincrement, " //$NON-NLS-1$ 
    +CyclesDBAdapter.NAME+" TEXT," //$NON-NLS-1$ 
    +CyclesDBAdapter.MODEL+" TEXT," //$NON-NLS-1$ 
    +CyclesDBAdapter.YEAR+" TEXT"+ ");"; //$NON-NLS-1$ //$NON-NLS-2$ 


    private final Context context; 
    private DatabaseHelper DBHelper; 
    private SQLiteDatabase db; 

    /** 
    * Constructor 
    * @param ctx 
    */ 
    public DBAdapter(Context ctx) 
    { 
     this.context = ctx; 
     this.DBHelper = new DatabaseHelper(this.context); 
    } 

    private static class DatabaseHelper extends SQLiteOpenHelper 
    { 
     DatabaseHelper(Context context) 
     { 
      super(context, DATABASE_NAME, null, DATABASE_VERSION); 
     } 

     @Override 
     public void onCreate(SQLiteDatabase db) 
     { 
      db.execSQL(CREATE_TABLE_CARS); 
      db.execSQL(CREATE_TABLE_BOATS); 
      db.execSQL(CREATE_TABLE_CYCLES);   
     } 

     @Override 
     public void onUpgrade(SQLiteDatabase db, int oldVersion, 
     int newVersion) 
     {    
      // Adding any table mods to this guy here 
     } 
    } 

    /** 
    * open the db 
    * @return this 
    * @throws SQLException 
    * return type: DBAdapter 
    */ 
    public DBAdapter open() throws SQLException 
    { 
     this.db = this.DBHelper.getWritableDatabase(); 
     return this; 
    } 

    /** 
    * close the db 
    * return type: void 
    */ 
    public void close() 
    { 
     this.DBHelper.close(); 
    } 
} 

La clase DBAdapter solo se invoca cuando la aplicación se inicia por primera vez y su única responsabilidad es crear/actualizar las tablas. El resto del acceso a los datos se realiza a través de la clase individual "adaptador". Descubrí que esto funciona perfectamente y no crea los problemas de versiones que mencioné anteriormente.

Espero que esto ayude.

+3

¿Qué sucede si necesita unir mesas? ¿puedes hacer esto? –

+1

Supongo que tiene acceso global a la base de datos desde cada adaptador, por lo que unir las tablas no debería ser un problema. Todavía me pregunto si tener varias subclases de SqliteOpenHelper dentro de cada adaptador es un buen enfoque ... – Mirko

+19

Esta es una gran solución, pero si pudiera hacer una sugerencia para mejorarla aún más: tendría mis adaptadores de tabla individuales 'extender' la clase DBAdapter, lo que obligaría a la base de datos a verificar si necesita actualizar cuando' open() 'cualquiera de los adaptadores de mesa. También elimina la necesidad de crear instancias de DBAdapter cuando la aplicación se inicia por primera vez. – Matt

9

Tuve el mismo problema, intenté con muchas soluciones, finalmente hice un método abstracto que construye la estructura de la base de datos y tiene una clase extendida para las clases de tabla.

Ésta es mi clase de base de datos constructor y es Resumen:

public abstract class dbAdapter { 
    public static String DATABASE_NAME = ""; 
    public static final int DATABASE_VERSION = 1; 
    public static final String DATABASE_TABLE1 = "ContactName"; 
    public static final String DATABASE_TABLE2 = "PhoneNumber"; 

    public static DbHelper ourHelper; 
    public static Context ourContext; 
    public static SQLiteDatabase ourDatabase; 

    boolean ourConstructorBool = false; 
    boolean ourDB = false; 

    public static final String ContactNameTable = "CREATE TABLE "+DATABASE_TABLE1+" (" + 
     ContactNameAdapter.KEY_ROWID+" INTEGER PRIMARY KEY AUTOINCREMENT, " + 
     ContactNameAdapter.KEY_NAME+" TEXT, " + 
     ContactNameAdapter.KEY_BIRTH_DATE+" TEXT);"; 

    public static final String PhoneNumberTable = "CREATE TABLE "+DATABASE_TABLE2+" (" + 
     PhoneNumberAdapter.KEY_NUMBER+" TEXT , " + 
     PhoneNumberAdapter.KEY_DESCRIPTION+" TEXT, " + 
     PhoneNumberAdapter.KEY_CONTACTID+" TEXT, " + 
     "FOREIGN KEY(" + PhoneNumberAdapter.KEY_CONTACTID +") REFERENCES " + 
     (ContactNameAdapter.DATABASE_TABLE)+"("+ContactNameAdapter.KEY_ROWID+") ON DELETE CASCADE"+ 
    ");"; 

    static class DbHelper extends SQLiteOpenHelper{ 
     public DbHelper(Context context) { 
      super(context, DATABASE_NAME, null, DATABASE_VERSION); 
     } 
     @Override 
     public void onCreate(SQLiteDatabase db) { 
      db.execSQL(ContactNameTable); 
      db.execSQL(PhoneNumberTable); 
     } 

     @Override 
     public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) { 
      db.execSQL("DROP TABLE IF EXISTS " + ContactNameAdapter.DATABASE_TABLE); 
      db.execSQL("DROP TABLE IF EXISTS " + PhoneNumberAdapter.DATABASE_TABLE); 
      onCreate(db); 
     } 
    } 

    public dbAdapter(Activity a){ 
     if(!ourConstructorBool == true){ 
      ourContext = a; 
      DATABASE_NAME = a.getString(Asaf.com.contactsEX.R.string.DB_NAME); 
      ourConstructorBool = true; 
     } 
    } 

    public dbAdapter open() throws SQLException{ 
     if(!ourDB == true){ 
      ourHelper = new DbHelper(ourContext); 
      ourDB = true; 
     } 
     ourDatabase = ourHelper.getWritableDatabase(); 
     return this; 
    } 

    public void close(){ 
     if(ourDatabase.isOpen()) 
      ourHelper.close(); 
    } 
} 

Y esta es una de mis clases de mesa, el resto de las clases se implementa el mismo, sólo tiene que añadir tanto como te gusta:

public class PhoneNumberAdapter extends dbAdapter{ 

    public static final String KEY_NUMBER = "PhoneNumber"; 
    public static final String KEY_DESCRIPTION = "Description"; 
    public static final String KEY_CONTACTID = "ContactName_id"; 

    public static final String DATABASE_TABLE = "PhoneNumber"; 

    public PhoneNumberAdapter(Activity a){ 
     super(a); 
    } 

    public long createEntry(String number, String description,long id){ 
     // TODO Auto-generated method stub 
     ContentValues cv = new ContentValues(); 
     cv.put(KEY_NUMBER, number); 
     cv.put(KEY_DESCRIPTION, description); 
     cv.put(KEY_CONTACTID, id); 
     return ourDatabase.insert(DATABASE_TABLE, null,cv); 
    } 
} 

Hope I help.

+2

¿Por qué pasas una instancia de actividad a tus constructores? ¿No debería ser un contexto? – Mirko

+0

¿Cómo puede 'ourConstructorBool' puede ser 'verdadero' si no está declarado como 'estático'? – Ohmnibus

Cuestiones relacionadas