Multiple Table SQLite DB Adapter (s) W Androidzie?

Czytałem samouczek Android SQLite NotePad, który odwoływał się do tworzenia klasy adaptera DB, aby utworzyć i uzyskać dostęp do tabeli DB. Czy w przypadku wielostolikowej bazy danych SQLite najlepiej jest utworzyć inną klasę adaptera dla każdej tabeli lub utworzyć pojedynczą klasę adaptera DB dla całej aplikacji na Androida?

Moja aplikacja używa wielu tabel i miałem nadzieję, że nie będę musiał mieć jednej masywnej klasy adaptera. problem jednak polega na tym, że mam zagnieżdżone podklasa SQLiteOpenHelper na przykładzie notatnika w każdym adapterze. Po wejściu do pierwszej tabeli wszystko działa poprawnie. Kiedy próbuję uzyskać dostęp do drugiego tble (z innej aktywności) moja aplikacja ulega awarii.

Początkowo myślałem, że awaria była spowodowana przez problem z wersjonowaniem, ale oba adaptery mają teraz tę samą wersję bazy danych i nadal się zawiesza.

Oto przykład jednego z adapterów DB dla tabeli. Wszystkie pozostałe adaptery są zgodne z tym samym formatem różne implementacje.

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$
    }
}

Kiedy pierwszy adapter, w tym przypadku usersinfo, jest dostępny, wszystko działa zgodnie z oczekiwaniami. Załóżmy, że mam inny adapter dla informacji o przyjaciołach, który podąża za tą samą strukturą jak powyżej, gdy jest dostępny przez inną aktywność, wydaje mi się, że zagnieżdżona podklasa SQLiteOpenHelper spróbuje ponownie utworzyć bazę danych. Oczywiście coś jest nie tak, ponieważ w tym scenariuszu moja aplikacja ulega awarii.

Tak jest standardowa praktyka w Android stworzy pojedynczy Adapter db zamiast pojedynczych adapterów na stół?

Author: OMG Ponies, 2010-10-31

2 answers

Oto rozwiązanie, które ostatecznie wdrożyłem. Jest to rodzaj mash-up z informacji uzyskanych w książkach Commonsware i niektórych rzeczy w internecie, które chciałbym zaksięgować bo chcę dać kredyt: {]}

Dla każdego typu danych, które muszę pobrać z db, tworzę klasę "adapter" (nie podklasowaną z niczego). Te klasy adapterów zawierają wszystkie metody niezbędne do uzyskania dostępu do bazy danych dla tego fragmentu informacji. Na przykład, gdybym miał trzy stoliki w moim db:

  1. samochody
  2. łodzie
  3. motocykle

Miałbym trzy adaptery, które wyglądałyby podobnie do poniższych (wstawiam tylko jeden jako demo, ale pomysł jest taki sam dla każdego):

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

}

Więc jeśli wyobrażasz sobie, że mam jedną z tych klas "adaptery" dla każdej tabeli.

Kiedy zaczyna się mój ekran powitalny aplikacji, używam techniki przedstawionej Android dla początkujących: tworzenie wielu tabel SQLite dla Androida

Więc mój główny DBAdapter (który jest odpowiedzialny za tworzenie wszystkich moich tabel w jednym db) wygląda tak:

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

Klasa DBAdapter jest wywoływana tylko przy pierwszym uruchomieniu aplikacji i jej jedynym obowiązkiem jest tworzenie / uaktualnianie tabel. Cały inny dostęp do danych odbywa się za pośrednictwem indywidualnej klasy "adapter". Odkryłem, że działa to doskonale i nie tworzy problemów z wersjonowaniem, o których wspomniałem wcześniej.

Mam nadzieję, że to pomoże.
 80
Author: Shawn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-05 14:12:04

Miałem ten sam problem, próbowałem wielu rozwiązań, finnaly zrobiłem abstrakcyjną metodę, która konstruuje strukturę bazy danych i ma rozszerzoną klasę dla klas table.

To jest moja klasa konstruktora bazy danych i jest abstrakcyjna:

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

A to jest jedna z moich klas tabelkowych, reszta klas jest zaimplementowana tak samo, wystarczy dodać tyle ile chcesz:

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);
    }
}
Mam nadzieję, że pomogłem.
 9
Author: Asaf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-11 11:07:37