Синхронизация SQLiteOpenHelper

Итак, я придумал какую-то идею, и мне интересно, реализуема ли она.

Допустим, у меня есть несколько таблиц (моделей базы данных), каждая из которых представлена ​​некоторым классом. Я не буду использовать шаблон singleton с открытым помощником, поэтому я создал простой класс для предоставления одного экземпляра базы данных. Мой Идея заключается в том, что пока все таблицы содержат ссылку на SQLiteDatabase (возвращенную открытым помощником), все они будут работать с одним и тем же экземпляром БД и, вероятно, не понадобятся для синхронизации работы с базой данных, поскольку это делает открытый помощник. Когда последняя таблица завершает свою работу, GC собирает открытый помощник (поскольку последняя ссылка будет слабой ссылкой) -> вызывается finalize(), и я закрываю базу данных во время этого метода, чтобы предотвратить любое предупреждение от ОС. Мой вопрос: может ли это работать? Будет ли он автоматически закрывать БД и будет ли он протекать или выдавать какое-то исключение?

Вот мой класс:

public class DatabaseHelper {

private static WeakReference<SomeCustomOpenHelper> sDBOpenHelper;

private void notifyDBCreate(SQLiteDatabase db) {
    for (DBTable table : mTables) {
        table.onDBCreate(db);
    }
}

private void notifyDBUpgrade(SQLiteDatabase db) {
    for (DBTable table : mTables) {
        table.onDBUpgrade(db);
    }
}

public SQLiteDatabase getDatabase(boolean readOnly) {
    SomeCustomOpenHelper dbHelper = sDBOpenHelper.get();
    if (dbHelper == null) {
        dbHelper = new SomeCustomOpenHelper(context, name, factory, version, new DatabaseEventsCallback());
        sDBOpenHelper = new WeakReference<SomeCustomOpenHelper>(dbHelper);
    }
    if (readOnly) {
        return dbHelper.getReadableDatabase();
    } else {
        return dbHelper.getWritableDatabase();
    }
}

private class DatabaseEventsCallback implements IDatabaseEventsCallback {

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

    @Override
    public void onUpgrade(SQLiteDatabase db) {
         notifyDBUpgrade(db);
    }

}

interface IDatabaseEventsCallback {
    void onCreate(SQLiteDatabase db);

    void onUpgrade(SQLiteDatabase db);
}

private static class SomeCustomOpenHelper extends SQLiteOpenHelper {

    private IDatabaseEventsCallback mCB;

    public SomeCustomOpenHelper(Context context, String name, CursorFactory factory, int version, IDatabaseEventsCallback cb) {
        super(context, name, factory, version);

        mCB = cb;
    }

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

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

    @Override
    protected void finalize() throws Throwable {
        this.close();
        super.finalize();
    }
}
}

person Teodor    schedule 27.03.2013    source источник
comment
Почему именно вы не хотите использовать шаблон singleton для своего OpenHelper?   -  person Stefan de Bruijn    schedule 27.03.2013
comment
Есть несколько тем, в которых синглтон описывается как антипаттерн. Мои две основные причины: он должен оставаться в памяти в течение жизненного цикла приложения и не может быть протестирован/расширен.   -  person Teodor    schedule 27.03.2013
comment
нет ничего против использования слабой ссылки в одноэлементном шаблоне, что, кажется, в основном то, что вы делаете.   -  person njzk2    schedule 02.04.2013
comment
Это не одноэлементный шаблон, так как я могу иметь несколько вспомогательных объектов базы данных во всех классах таблиц, которые все будут совместно использовать один sqliteopenhelper. Идея состоит в том, что все они будут совместно использовать один экземпляр, пока кто-либо использует этот экземпляр. Когда последний держатель sqliteopenhelper выпустит этот экземпляр, он будет gc-ed и, вероятно, должным образом db закрыт. Мой вопрос в том, будет ли это работать так или что-то пойдет не так :)   -  person Teodor    schedule 03.04.2013
comment
да, поэтому ваш синглтон sqliteopenhelper, инкапсулированный в weakref. это другой способ сделать это, но он по-прежнему выглядит для меня как синглтон в том смысле, что он ограничивает создание экземпляра класса одним объектом.   -  person njzk2    schedule 03.04.2013


Ответы (5)


Тоже не знал ответа, но заинтересовался и посмотрел.

Ответ написан правильно здесь; http://blog.foxxtrot.net/2009/01/a-sqliteopenhelper-is-not-a-sqlitetablehelper.html

Но в основном суть информации такова;

Я создал три класса SQLiteOpenHelper, по одному для каждой таблицы, хотя все они ссылались только на один файл базы данных.

Вот тут все развалилось. Android поддерживает версии для баз данных на основе пакета, с которым он связан, имени базы данных и предоставленного вами номера версии. Пакет и имя определяют, каким будет путь на устройстве, в то время как версия хранится (где-то) на устройстве, чтобы оно знало, когда ему нужно вызвать обработчик события onUpgrade OpenHelper. Оказывается, если в конструкторе SQLiteOpenHelper он определяет, что база данных уже существует, он вообще не будет вызывать ваши методы onCreate или onUpgrade, даже если конкретный класс, выполняющий вызов, никогда раньше не вызывался.

person Stefan de Bruijn    schedule 27.03.2013
comment
Моя идея состоит в том, что все используют один открытый помощник, а не один открытый помощник на таблицу. - person Teodor; 27.03.2013

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

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

Дайте мне знать, если это работает или есть лучший обходной путь.

person Gaurav Arora    schedule 04.04.2013

Вы можете сделать это. Как вы говорите, блокировка должна происходить на SQLite, и я никогда не слышал проблем, связанных с этим, так что вы должны быть в порядке. Единственное ограничение, которое у вас есть, заключается в том, что все таблицы должны будут войти в одну и ту же базу данных, поскольку Android пока позволяет вам иметь только один файл.

Закрытие базы данных - это другое дело, поэтому на самом деле интересно использовать шаблон singleton (вы избегаете закрытия + открытия все время). Тем не менее, с вашим подходом вам просто нужно обязательно закрыть базу данных, когда вы закончите с ней. Насколько я понимаю, это не делается автоматически.

Кроме того, Ларс Фогель написал чрезвычайно полезные и подробные статьи о доступе к БД в Android. Возможно, вы захотите взглянуть туда. http://www.vogella.com/articles/AndroidSQLite/article.html

person Jose L Ugia    schedule 01.04.2013
comment
Да, но здесь закрытие базы данных происходит, когда GC собирает пользовательский вспомогательный класс. Он будет собран, когда будет освобожден последний объект базы данных, поскольку останется только ссылка Weak (внешнего класса) - person Teodor; 02.04.2013
comment
Обратите внимание, что избавление от любого объекта в памяти, который распространяется из любого помощника открытия базы данных, не закрывает для вас базу данных. На самом деле БД останется открытой, если вы этого не сделаете. Из документации в SQLiteOpenHelper(developer.android.com/reference/android/ database/sqlite/): (Обязательно вызовите {@link #close}, когда вам больше не нужна база данных.) - person Jose L Ugia; 02.04.2013
comment
Android на данный момент позволяет вам иметь только один файл. что ты имеешь в виду ? - person njzk2; 02.04.2013
comment
Как вы можете видеть в классе SomeCustomHelper в методе finalize() db закрыт. - person Teodor; 03.04.2013
comment
Я бы не использовал finalize для этой цели. Обратите внимание, что вы не уверены, когда и как сборщик мусора позаботится о вашем объекте. Например, может случиться так, что это займет гораздо больше времени, чем ожидалось. Кроме того, рекомендуется позаботиться о закрывающихся объектах, как только ресурс больше не нужен. Проверьте это для получения дополнительной информации. developer.android.com/reference/java/lang/ - person Jose L Ugia; 03.04.2013

вы можете использовать один открытый помощник для всей таблицы. Я также использую один экземпляр в своем приложении.

public static synchronized DatabaseHelper getInstance(Context ctx)
    {
        if (dbhelper == null) {

            dbhelper = new DatabaseHelper(ctx);
        }

        return dbhelper ;
    }
person Monty    schedule 02.04.2013
comment
так что вы в основном используете синглтон. второе предложение в вопросе читается как I don't wont to use singleton pattern. Просто говорю - person njzk2; 02.04.2013
comment
тогда он может каждый раз создавать новый экземпляр... в чем проблема. или может быть я не понимаю вопрос... спасибо - person Monty; 03.04.2013
comment
Создание нескольких экземпляров sqliteopenhelper может вызвать исключение, когда один экземпляр записывает в базу данных, а другой пытается читать/записывать. Наличие только одного экземпляра за раз обеспечивает некоторую синхронизацию. Давайте сначала проведем различие между одноэлементным и единичным экземпляром. И есть так много статей, почему одноэлементный шаблон следует рассматривать как анти-шаблон. Я не говорю, что это бесполезно, поскольку в некоторых случаях синглтон является лучшим решением. Но я не уверен, что этот случай необходим, поэтому я спрашиваю, может ли это работать или может где-то сломаться. - person Teodor; 03.04.2013
comment
Мне интересно, есть ли у вас точное сравнение между одиночным экземпляром и одноэлементным - person njzk2; 03.04.2013

Мой вопрос: может ли это работать? Будет ли он автоматически закрывать БД и будет ли он протекать или выдавать какое-то исключение?

НЕТ, он не будет автоматически закрывать базу данных, когда ваше приложение потребует объект DATABASE, а ОС обнаружила, что некоторые из ваших мгновенных баз данных живы, тогда платформа Android попытается подключить эту ссылку на объект (которая, вероятно, является слабой ссылкой)

и я должен сказать, я не рекомендую открывать и закрывать БАЗЫ ДАННЫХ по запросу или временно. Всегда приятно открывать БД заранее и держать ее открытой на протяжении всей вашей деятельности и закрывать ее, когда деятельность завершается или приостанавливается.

person dharam    schedule 05.04.2013
comment
Вероятно, вы не видите всей идеи. OpenHelper содержит ссылку на sqltiedatabase и обеспечивает синхронизацию. Я буду использовать только эту ссылку, пока кто-то1 ее использует. t быть закрытым. Когда не останется сильных ссылок, последняя будет в классе-оболочке (и это слабая ссылка), поэтому gc соберет эту базу данных -> финализировать -> закрыть. В следующий раз, когда кто-то вызовет getDB(), он создаст новый экземпляр для открытого помощника и предоставит тот же экземпляр, пока кто-то его использует. - person Teodor; 05.04.2013
comment
пожалуйста, обратитесь к этому вопросу SO /6905524/ - person dharam; 06.04.2013