एंड्रॉइड पर SQLite के लिए सर्वोत्तम अभ्यास क्या हैं?


694

एंड्रॉइड ऐप के भीतर SQLite डेटाबेस पर प्रश्नों को निष्पादित करते समय सर्वोत्तम प्रथाओं को क्या माना जाएगा?

क्या यह एक AsyncTask के doInBackground से आवेषण, विलोपन और प्रश्नों को चलाने के लिए सुरक्षित है? या मुझे यूआई थ्रेड का उपयोग करना चाहिए? मुझे लगता है कि डेटाबेस क्वेरी "भारी" हो सकती है और यूआई थ्रेड का उपयोग नहीं करना चाहिए क्योंकि यह ऐप को लॉक कर सकता है - जिसके परिणामस्वरूप एप्लिकेशन नॉट रिस्पॉन्सिंग (एएनआर) होगा।

अगर मेरे पास कई AsyncTasks हैं, तो क्या उन्हें कनेक्शन साझा करना चाहिए या क्या उन्हें प्रत्येक कनेक्शन खोलना चाहिए?

क्या इन परिदृश्यों के लिए कोई सर्वोत्तम प्रथाएं हैं?


10
जो कुछ भी आप करते हैं, अगर आपके कंटेंट प्रोवाइडर (या SQLite इंटरफ़ेस) को सार्वजनिक रूप से सामना करना पड़ रहा है तो अपने इनपुट्स को याद रखें!
क्रिस्टोफर मिकिंस्की

37
आपको निश्चित रूप से UI थ्रेड से db एक्सेस नहीं करना चाहिए, मैं आपको इतना बता सकता हूं।
एडवर्ड फाल्क

@EdwardFalk क्यों नहीं? निश्चित रूप से ऐसे मामले हैं जहां ऐसा करना वैध है?
माइकल

4
यदि आप UI थ्रेड से कोई I / O, नेटवर्क एक्सेस आदि करते हैं, तो ऑपरेशन पूरा होने तक पूरा डिवाइस फ्रीज हो जाता है। यदि यह 1/20 सेकंड में पूरा होता है, तो ठीक है। यदि यह अधिक समय लेता है, तो आपको एक बुरा उपयोगकर्ता अनुभव मिला है।
एडवर्ड फॉक

जवाबों:


631

आम तौर पर कई थ्रेड्स से अपडेट, अपडेट, डिलीट और रीड्स ठीक होते हैं, लेकिन ब्रैड का जवाब सही नहीं है। आपको अपने कनेक्शन बनाने और उनका उपयोग करने के तरीके से सावधान रहना होगा। ऐसी स्थितियां हैं जहां आपके अपडेट कॉल विफल हो जाएंगे, भले ही आपका डेटाबेस दूषित न हो।

मूल उत्तर।

SqliteOpenHelper ऑब्जेक्ट एक डेटाबेस कनेक्शन पर रहता है। यह आपको एक रीड एंड राइट कनेक्शन पेश करता है, लेकिन यह वास्तव में नहीं है। केवल पढ़ने के लिए कॉल करें, और आपको बिना परवाह किए राइट डेटाबेस कनेक्शन मिल जाएगा।

तो, एक सहायक उदाहरण, एक डीबी कनेक्शन। भले ही आप इसे कई थ्रेड से उपयोग करते हैं, एक बार में एक कनेक्शन। SqliteDatabase ऑब्जेक्ट का उपयोग क्रमबद्ध रखने के लिए जावा लॉक का उपयोग करता है। इसलिए, यदि 100 थ्रेड्स में एक db उदाहरण है, तो वास्तविक ऑन-डिस्क डेटाबेस पर कॉल को क्रमबद्ध किया जाता है।

तो, एक सहायक, एक डीबी कनेक्शन, जो जावा कोड में क्रमबद्ध है। एक धागा, 1000 धागे, यदि आप उनके बीच साझा किए गए एक सहायक उदाहरण का उपयोग करते हैं, तो आपके सभी डीबी एक्सेस कोड सीरियल हैं। और जीवन अच्छा है (ईश)।

यदि आप एक ही समय में वास्तविक अलग-अलग कनेक्शनों से डेटाबेस में लिखने का प्रयास करते हैं, तो एक असफल हो जाएगा। यह तब तक इंतजार नहीं करेगा जब तक पहले किया जाता है और फिर लिखना होता है। यह बस अपने परिवर्तन नहीं लिखेंगे। इससे भी बदतर, अगर आप SQLiteDatabase पर इन्सर्ट / अपडेट के सही संस्करण को नहीं कहते हैं, तो आपको अपवाद नहीं मिलेगा। आपको बस अपने LogCat में एक संदेश मिलेगा, और वह यह होगा।

तो, कई धागे? एक सहायक का उपयोग करें। अवधि। यदि आप जानते हैं कि केवल एक धागा लिख ​​रहा होगा, तो आप कई कनेक्शन का उपयोग करने में सक्षम होंगे, और आपकी रीड तेजी से होगी, लेकिन खरीदार सावधान रहें। मैंने उतना परीक्षण नहीं किया है।

यहाँ एक ब्लॉग पोस्ट है जिसमें अधिक विस्तार और एक उदाहरण ऐप है।

ग्रे और मैं वास्तव में एक ओआरएम टूल को लपेट रहे हैं, जो कि उसके ओर्लीमाइट पर आधारित है, जो मूल रूप से एंड्रॉइड डेटाबेस कार्यान्वयन के साथ काम करता है, और मैं ब्लॉग पोस्ट में वर्णित सुरक्षित निर्माण / कॉलिंग संरचना का अनुसरण करता हूं। यह बहुत जल्द खत्म हो जाना चाहिए। जरा देखो तो।


इस बीच, एक अनुवर्ती ब्लॉग पोस्ट है:

इसके अलावा पहले उल्लिखित लॉकिंग उदाहरण के 2point0 द्वारा कांटा चेक करें :


2
एक तरफ के रूप में, Ormlite का एंड्रॉइड सपोर्ट ormlite.sourceforge.net/sqlite_java_android_orm.html पर पाया जा सकता है । नमूना परियोजनाएं, प्रलेखन और जार हैं।
ग्रे

1
एक दूसरी तरफ। Ormlite कोड में सहायक कक्षाएं होती हैं जिनका उपयोग dbhelper इंस्टेंस को प्रबंधित करने के लिए किया जा सकता है। आप ormlite सामान का उपयोग कर सकते हैं, लेकिन इसकी आवश्यकता नहीं है। आप कनेक्शन प्रबंधन करने के लिए केवल सहायक कक्षाओं का उपयोग कर सकते हैं।
केविन गैलिगन

31
कागि, विस्तृत विवरण के लिए धन्यवाद। क्या आप एक बात स्पष्ट कर सकते हैं - मैं समझता हूं कि आपके पास एक सहायक होना चाहिए, लेकिन क्या आपके पास केवल एक कनेक्शन (यानी एक SqliteDatabase वस्तु) होना चाहिए? दूसरे शब्दों में, आपको कितनी बार getWritableDatabase को कॉल करना चाहिए? और समान रूप से महत्वपूर्ण रूप से जब आप पास बुलाते हैं ()?
आर्टेम

3
मैंने कोड अपडेट किया। जब मैं ब्लॉग होस्ट बंद करता था, तो मूल खो जाता था, लेकिन मैंने कुछ स्लिम डाउन उदाहरण कोड जोड़ा जो कि इस मुद्दे को प्रदर्शित करेगा। इसके अलावा, आप एकल कनेक्शन कैसे प्रबंधित करते हैं? मेरे पास शुरू में बहुत अधिक जटिल समाधान था, लेकिन मैंने तब से संशोधन किया है। यहाँ एक नज़र डालें: टचलैब
केविन गैलिगन

क्या कनेक्शन को निपटाने की आवश्यकता है और इसे कहां करना है?
tugce

189

समवर्ती डेटाबेस एक्सेस

मेरे ब्लॉग पर एक ही लेख (मुझे अधिक प्रारूपण पसंद है)

मैंने छोटा लेख लिखा है जो आपके एंड्रॉइड डेटाबेस थ्रेड को सुरक्षित बनाने के तरीके का वर्णन करता है।


मान लें कि आपके पास अपनी खुद की SQLiteOpenHelper है

public class DatabaseHelper extends SQLiteOpenHelper { ... }

अब आप अलग-अलग थ्रेड्स में डेटा को डेटाबेस में लिखना चाहते हैं।

 // Thread 1
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

 // Thread 2
 Context context = getApplicationContext();
 DatabaseHelper helper = new DatabaseHelper(context);
 SQLiteDatabase database = helper.getWritableDatabase();
 database.insert(…);
 database.close();

आपको अपने लॉगकैट में निम्न संदेश मिलेगा और आपका कोई भी परिवर्तन नहीं लिखा जाएगा।

android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)

ऐसा इसलिए हो रहा है क्योंकि हर बार जब आप नई SQLiteOpenHelper ऑब्जेक्ट बनाते हैं तो आप वास्तव में नया डेटाबेस कनेक्शन बना रहे होते हैं। यदि आप एक ही समय में वास्तविक अलग-अलग कनेक्शन से डेटाबेस में लिखने का प्रयास करते हैं, तो एक असफल हो जाएगा। (उपरोक्त उत्तर से)

एक से अधिक थ्रेड वाले डेटाबेस का उपयोग करने के लिए हमें यह सुनिश्चित करना होगा कि हम एक डेटाबेस कनेक्शन का उपयोग कर रहे हैं।

आइए सिंगलटन क्लास डेटाबेस मैनेजर बनाते हैं जो एकल SQLiteOpenHelper ऑब्जेक्ट को रखेगा और वापस लौटाएगा

public class DatabaseManager {

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initialize(..) method first.");
        }

        return instance;
    }

    public SQLiteDatabase getDatabase() {
        return new mDatabaseHelper.getWritableDatabase();
    }

}

अद्यतित कोड जो अलग थ्रेड्स में डेटाबेस में डेटा लिखते हैं, वह इस तरह दिखेगा।

 // In your application class
 DatabaseManager.initializeInstance(new MySQLiteOpenHelper());
 // Thread 1
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

 // Thread 2
 DatabaseManager manager = DatabaseManager.getInstance();
 SQLiteDatabase database = manager.getDatabase()
 database.insert(…);
 database.close();

यह आपको एक और दुर्घटना लाएगा।

java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase

हम केवल एक डेटाबेस कनेक्शन का उपयोग कर रहे हैं, विधि getDatabase () का एक ही उदाहरण लौट SQLiteDatabase के लिए वस्तु Thread1 और Thread2 । क्या हो रहा है, थ्रेड 1 डेटाबेस को बंद कर सकता है, जबकि थ्रेड 2 अभी भी इसका उपयोग कर रहा है। इसलिए हमारे पास IllegalStateException क्रैश है।

हमें यह सुनिश्चित करने की आवश्यकता है कि कोई भी डेटाबेस का उपयोग नहीं कर रहा है और केवल तब इसे बंद करें। स्टैकविस्टफ़्लो पर कुछ लोगों ने आपके SQLiteDatabase को कभी बंद नहीं करने की सिफारिश की । यह लॉगकोट संदेश का अनुसरण करेगा।

Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed

काम का नमूना

public class DatabaseManager {

    private int mOpenCounter;

    private static DatabaseManager instance;
    private static SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mDatabase;

    public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
        if (instance == null) {
            instance = new DatabaseManager();
            mDatabaseHelper = helper;
        }
    }

    public static synchronized DatabaseManager getInstance() {
        if (instance == null) {
            throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                    " is not initialized, call initializeInstance(..) method first.");
        }

        return instance;
    }

    public synchronized SQLiteDatabase openDatabase() {
        mOpenCounter++;
        if(mOpenCounter == 1) {
            // Opening new database
            mDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mDatabase;
    }

    public synchronized void closeDatabase() {
        mOpenCounter--;
        if(mOpenCounter == 0) {
            // Closing database
            mDatabase.close();

        }
    }

}

इसे निम्नानुसार उपयोग करें।

SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way

हर बार जब आप डेटाबेस की जरूरत है आप बुलाना चाहिए openDatabase () की विधि DatabaseManager वर्ग। इस पद्धति के अंदर, हमारे पास एक काउंटर है, जो इंगित करता है कि कितनी बार डेटाबेस खोला गया है। यदि यह एक के बराबर है, तो इसका मतलब है कि हमें नया डेटाबेस कनेक्शन बनाने की आवश्यकता है, यदि नहीं, तो डेटाबेस कनेक्शन पहले से ही बनाया गया है।

ऐसा ही closeDatabase () मेथड मे होता है । जब भी हम इस विधि को कहते हैं, काउंटर कम हो जाता है, जब भी यह शून्य पर जाता है, हम डेटाबेस कनेक्शन को बंद कर रहे हैं।


अब आपको अपने डेटाबेस का उपयोग करने में सक्षम होना चाहिए और सुनिश्चित करें कि यह थ्रेड सुरक्षित है।


10
मैं उन लोगों में से एक हूं जो सुझाव देते हैं कि आप कभी भी डीबी को बंद नहीं करते हैं। आपको केवल "लीक पाया गया" त्रुटि मिलती है यदि आप डीबी खोलते हैं, तो इसे बंद न करें, फिर दोबारा खोलने का प्रयास करें। यदि आप केवल एक खुले सहायक का उपयोग करते हैं, और db को कभी बंद नहीं करते हैं, तो आपको वह त्रुटि नहीं मिलती है। यदि आप अन्यथा पाते हैं, तो कृपया मुझे बताएं (कोड के साथ)। मैं कहीं इसके बारे में एक लंबी पोस्ट था, लेकिन यह नहीं मिल सकता है। यहाँ कॉमन्सवेयर द्वारा उत्तर दिया गया प्रश्न, एसओ पॉइंट डिपार्टमेंट में हम दोनों को किस तरह का आउटलुक है: stackoverflow.com/questions/7211941/…
केविन गैलिगन

4
अधिक विचार। # 1, मैं आपके प्रबंधक के अंदर सहायक बनाऊंगा। इसे बाहर करने के लिए समस्याओं के लिए पूछना। नए देव किसी सहायक कारण से सीधे सहायक को बुला सकते हैं। इसके अलावा, यदि आप एक init विधि की आवश्यकता करने जा रहे हैं, तो अपवाद छोड़ें यदि उदाहरण पहले से मौजूद है। मल्टी-डीबी एप्लिकेशन स्पष्ट रूप से विफल रहेंगे। # 2, क्यों mDatabase क्षेत्र? इसके सहायक से उपलब्ध है। # 3, "कभी बंद न होने" की दिशा में आपका पहला कदम, आपके ऐप के क्रैश होने और "बंद" न होने पर आपके db का क्या होगा? संकेत, कुछ भी नहीं। यह ठीक है, क्योंकि SQLite सुपर स्थिर है। आपको यह बंद करने की आवश्यकता क्यों है , यह पता लगाने में चरण 1 था ।
केविन गैलिगन

1
किसी भी कारण से आप उदाहरण प्राप्त करने से पहले कॉल करने के लिए एक सार्वजनिक इनिशियलाइज़ विधि का उपयोग करते हैं? एक निजी कंस्ट्रक्टर को क्यों नहीं बुलाया जाता है if(instance==null)? आप हर बार शुरुआती कॉल करने के अलावा कोई विकल्प नहीं छोड़ते हैं; आपको यह कैसे पता चलेगा कि इसे अन्य अनुप्रयोगों आदि में आरंभ किया गया है या नहीं?
चीफवूवेन्सिल्स

1
initializeInstance()एक प्रकार का पैरामीटर है SQLiteOpenHelper, लेकिन आपकी टिप्पणी में आपने उपयोग करने का उल्लेख किया है DatabaseManager.initializeInstance(getApplicationContext());। क्या हो रहा है? यह संभवतः कैसे काम कर सकता है?
17

2
@DmytroDanylyk "DatabaseManager धागा सुरक्षित सिंगलटन है, इसलिए इसे कहीं भी इस्तेमाल किया जा सकता है" जो कि virsir के सवाल के जवाब में सही नहीं है। ऑब्जेक्ट साझा क्रॉस प्रक्रिया नहीं हैं। आपके डेटाबेस
प्रबंधक

17
  • एक का प्रयोग करें Threadया AsyncTaskलंबे समय से चल परिचालन (50ms +) के लिए। अपने एप्लिकेशन को यह देखने के लिए परीक्षण करें कि वह कहाँ है। अधिकांश परिचालनों (शायद) को एक धागे की आवश्यकता नहीं होती है, क्योंकि अधिकांश परिचालनों (शायद) में केवल कुछ पंक्तियाँ शामिल होती हैं। थोक संचालन के लिए एक धागे का उपयोग करें।
  • SQLiteDatabaseथ्रेड्स के बीच डिस्क पर प्रत्येक DB के लिए एक उदाहरण साझा करें और खुले कनेक्शन का ट्रैक रखने के लिए एक गिनती प्रणाली लागू करें।

क्या इन परिदृश्यों के लिए कोई सर्वोत्तम प्रथाएं हैं?

अपनी सभी कक्षाओं के बीच एक स्थिर क्षेत्र साझा करें। मैं उस और अन्य चीजों के लिए एक सिंगलटन रखता था जिसे साझा करने की आवश्यकता थी। एक गिनती योजना (आमतौर पर एटोमिकइंटर का उपयोग करके) का उपयोग यह सुनिश्चित करने के लिए भी किया जाना चाहिए कि आप डेटाबेस को कभी भी बंद नहीं करते हैं या इसे खुला नहीं छोड़ते हैं।

मेरा समाधान:

सबसे वर्तमान संस्करण के लिए, https://github.com/JakarCo/databasemanager देखें, लेकिन मैं यहां तक ​​कोड रखने की कोशिश करूंगा। यदि आप मेरे समाधान को समझना चाहते हैं, तो कोड को देखें और मेरे नोट्स पढ़ें। मेरे नोट्स आमतौर पर काफी मददगार होते हैं।

  1. कोड को एक नई फ़ाइल में कॉपी / पेस्ट करें DatabaseManager। (या इसे github से डाउनलोड करें)
  2. विस्तार DatabaseManagerऔर लागू करें onCreateऔर onUpgradeआप की तरह सामान्य रूप से होगा। DatabaseManagerडिस्क पर अलग-अलग डेटाबेस रखने के लिए आप एक वर्ग के कई उपवर्ग बना सकते हैं ।
  3. अपने उपवर्ग को तुरंत लिखें और कक्षा getDb()का उपयोग करने के लिए कॉल करें SQLiteDatabase
  4. close()प्रत्येक उपवर्ग के लिए कॉल करें जिसे आपने त्वरित किया था

कोड कॉपी / पेस्ट करने के लिए :

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import java.util.concurrent.ConcurrentHashMap;

/** Extend this class and use it as an SQLiteOpenHelper class
 *
 * DO NOT distribute, sell, or present this code as your own. 
 * for any distributing/selling, or whatever, see the info at the link below
 *
 * Distribution, attribution, legal stuff,
 * See https://github.com/JakarCo/databasemanager
 * 
 * If you ever need help with this code, contact me at support@androidsqlitelibrary.com (or support@jakar.co )
 * 
 * Do not sell this. but use it as much as you want. There are no implied or express warranties with this code. 
 *
 * This is a simple database manager class which makes threading/synchronization super easy.
 *
 * Extend this class and use it like an SQLiteOpenHelper, but use it as follows:
 *  Instantiate this class once in each thread that uses the database. 
 *  Make sure to call {@link #close()} on every opened instance of this class
 *  If it is closed, then call {@link #open()} before using again.
 * 
 * Call {@link #getDb()} to get an instance of the underlying SQLiteDatabse class (which is synchronized)
 *
 * I also implement this system (well, it's very similar) in my <a href="http://androidslitelibrary.com">Android SQLite Libray</a> at http://androidslitelibrary.com
 * 
 *
 */
abstract public class DatabaseManager {

    /**See SQLiteOpenHelper documentation
    */
    abstract public void onCreate(SQLiteDatabase db);
    /**See SQLiteOpenHelper documentation
     */
    abstract public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
    /**Optional.
     * *
     */
    public void onOpen(SQLiteDatabase db){}
    /**Optional.
     * 
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
    /**Optional
     * 
     */
    public void onConfigure(SQLiteDatabase db){}



    /** The SQLiteOpenHelper class is not actually used by your application.
     *
     */
    static private class DBSQLiteOpenHelper extends SQLiteOpenHelper {

        DatabaseManager databaseManager;
        private AtomicInteger counter = new AtomicInteger(0);

        public DBSQLiteOpenHelper(Context context, String name, int version, DatabaseManager databaseManager) {
            super(context, name, null, version);
            this.databaseManager = databaseManager;
        }

        public void addConnection(){
            counter.incrementAndGet();
        }
        public void removeConnection(){
            counter.decrementAndGet();
        }
        public int getCounter() {
            return counter.get();
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            databaseManager.onCreate(db);
        }

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

        @Override
        public void onOpen(SQLiteDatabase db) {
            databaseManager.onOpen(db);
        }

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

        @Override
        public void onConfigure(SQLiteDatabase db) {
            databaseManager.onConfigure(db);
        }
    }

    private static final ConcurrentHashMap<String,DBSQLiteOpenHelper> dbMap = new ConcurrentHashMap<String, DBSQLiteOpenHelper>();

    private static final Object lockObject = new Object();


    private DBSQLiteOpenHelper sqLiteOpenHelper;
    private SQLiteDatabase db;
    private Context context;

    /** Instantiate a new DB Helper. 
     * <br> SQLiteOpenHelpers are statically cached so they (and their internally cached SQLiteDatabases) will be reused for concurrency
     *
     * @param context Any {@link android.content.Context} belonging to your package.
     * @param name The database name. This may be anything you like. Adding a file extension is not required and any file extension you would like to use is fine.
     * @param version the database version.
     */
    public DatabaseManager(Context context, String name, int version) {
        String dbPath = context.getApplicationContext().getDatabasePath(name).getAbsolutePath();
        synchronized (lockObject) {
            sqLiteOpenHelper = dbMap.get(dbPath);
            if (sqLiteOpenHelper==null) {
                sqLiteOpenHelper = new DBSQLiteOpenHelper(context, name, version, this);
                dbMap.put(dbPath,sqLiteOpenHelper);
            }
            //SQLiteOpenHelper class caches the SQLiteDatabase, so this will be the same SQLiteDatabase object every time
            db = sqLiteOpenHelper.getWritableDatabase();
        }
        this.context = context.getApplicationContext();
    }
    /**Get the writable SQLiteDatabase
     */
    public SQLiteDatabase getDb(){
        return db;
    }

    /** Check if the underlying SQLiteDatabase is open
     *
     * @return whether the DB is open or not
     */
    public boolean isOpen(){
        return (db!=null&&db.isOpen());
    }


    /** Lowers the DB counter by 1 for any {@link DatabaseManager}s referencing the same DB on disk
     *  <br />If the new counter is 0, then the database will be closed.
     *  <br /><br />This needs to be called before application exit.
     * <br />If the counter is 0, then the underlying SQLiteDatabase is <b>null</b> until another DatabaseManager is instantiated or you call {@link #open()}
     *
     * @return true if the underlying {@link android.database.sqlite.SQLiteDatabase} is closed (counter is 0), and false otherwise (counter > 0)
     */
    public boolean close(){
        sqLiteOpenHelper.removeConnection();
        if (sqLiteOpenHelper.getCounter()==0){
            synchronized (lockObject){
                if (db.inTransaction())db.endTransaction();
                if (db.isOpen())db.close();
                db = null;
            }
            return true;
        }
        return false;
    }
    /** Increments the internal db counter by one and opens the db if needed
    *
    */
    public void open(){
        sqLiteOpenHelper.addConnection();
        if (db==null||!db.isOpen()){
                synchronized (lockObject){
                    db = sqLiteOpenHelper.getWritableDatabase();
                }
        } 
    }
}

1
जब आप "करीब" कहते हैं और तब कक्षा का फिर से उपयोग करने का प्रयास करते हैं तो क्या होता है? क्या यह दुर्घटनाग्रस्त हो जाएगा? या क्या यह स्वतः ही फिर से इनिशियलाइज़ कर लेगा कि फिर से DB का उपयोग करने में सक्षम हो?
Android डेवलपर

1
@androiddeveloper, यदि आप कॉल करते हैं close, तो आपको openकक्षा के समान उदाहरण का उपयोग करने से पहले फिर से कॉल करना होगा या आप एक नया उदाहरण बना सकते हैं। चूंकि closeकोड में, मैंने सेट किया है db=null, आप रिटर्न वैल्यू का उपयोग नहीं कर पाएंगे getDb(क्योंकि यह शून्य होगा), इसलिए NullPointerExceptionआपको ऐसा कुछ मिलेगा, जैसेmyInstance.close(); myInstance.getDb().query(...);
रीड

गठबंधन getDb()और open()एक विधि में क्यों नहीं ?
एलेक्स बर्दुलस

@ बर्डडू, डेटाबेस काउंटर और खराब डिज़ाइन पर अतिरिक्त नियंत्रण प्रदान करने का मिश्रण। यह निश्चित रूप से इसे करने का सबसे अच्छा तरीका नहीं है, हालांकि। मैं इसे कुछ दिनों में अपडेट कर दूंगा।
रीड

@ बर्डू, मैंने अभी इसे अपडेट किया है। आप यहाँ से नया कोड प्राप्त कर सकते हैं । मैंने इसका परीक्षण नहीं किया है, इसलिए कृपया मुझे बताएं कि क्या मुझे बदलाव करना चाहिए।
रीड

11

मल्टी-थ्रेडिंग के साथ डेटाबेस बहुत लचीला है। मेरे ऐप ने एक साथ कई अलग-अलग थ्रेड्स से अपने डीबी को मारा और यह ठीक है। कुछ मामलों में मेरे पास DB को एक साथ मारने वाली कई प्रक्रियाएँ हैं और यह ठीक भी काम करती हैं।

आपके async कार्य - जब आप कर सकते हैं तो उसी कनेक्शन का उपयोग करें, लेकिन यदि आपको करना है, तो विभिन्न कार्यों से DB तक पहुंचने के लिए इसका ठीक है।


इसके अलावा, क्या आपके पास विभिन्न कनेक्शनों में पाठक और लेखक हैं या क्या उन्हें एक ही कनेक्शन साझा करना चाहिए? धन्यवाद।
ग्रे

@ सही - सही, मुझे स्पष्ट रूप से उल्लेख करना चाहिए। जहां तक ​​कनेक्शन है, मैं यथासंभव कनेक्शन का उपयोग करूंगा, लेकिन चूंकि फाइलिंग सिस्टम स्तर पर लॉकिंग को संभाला जाता है, आप इसे कोड में कई बार खोल सकते हैं, लेकिन मैं यथासंभव एकल कनेक्शन का उपयोग करूंगा। Android sqlite DB बहुत लचीला और क्षमाशील है।
ब्रैड हेन

3
@Gray, बस उन लोगों के लिए एक अद्यतन जानकारी पोस्ट करना चाहता था जो इस पद्धति का उपयोग कर रहे हैं। दस्तावेज़ीकरण कहता है: यह विधि अब कुछ नहीं करती है। प्रयोग नहीं करें।
पीजूसन

3
मुझे यह विधि बुरी तरह से विफल हो गई है, हम कई अनुप्रयोगों से एक्सेस करने के लिए ContentProvider पर स्विच कर रहे हैं। हमें अपने तरीकों पर कुछ समसामयिकता करनी होगी, लेकिन एक ही समय में सभी डेटा तक पहुँचने वाली प्रक्रियाओं के साथ किसी भी समस्या को ठीक करना चाहिए।
जेपीएम

2
मुझे पता है कि यह पुराना है, लेकिन यह गलत है। यह अलग- अलग एस / एस पर अलग- अलग वस्तुओं से DB तक पहुंचने के लिए ठीक काम कर सकता है , लेकिन यह कभी-कभी त्रुटियों को जन्म देगा, यही वजह है कि SQLiteDatabase (पंक्ति 1297) sSQLiteDatabaseAsyncTaskThreadLock
रीड

7

Dmytro का जवाब मेरे मामले के लिए ठीक काम करता है। मुझे लगता है कि फ़ंक्शन को सिंक्रनाइज़ घोषित करना बेहतर है। कम से कम मेरे मामले के लिए, यह अशक्त सूचक अपवाद को अन्यथा आमंत्रित करेगा, जैसे getWritableDatabase अभी तक एक थ्रेड में नहीं लौटा और OpenDatabse दूसरे थ्रेड बीच में कहा जाता है।

public synchronized SQLiteDatabase openDatabase() {
    if(mOpenCounter.incrementAndGet() == 1) {
        // Opening new database
        mDatabase = mDatabaseHelper.getWritableDatabase();
    }
    return mDatabase;
}

mDatabaseHelper.getWritableDatabase (); इस नए डेटाबेस वस्तु बनाने नहीं होगा
दूरस्थ

5

कुछ घंटों के लिए इसके साथ संघर्ष करने के बाद, मैंने पाया है कि आप प्रति db निष्पादन में केवल एक db हेल्पर ऑब्जेक्ट का उपयोग कर सकते हैं। उदाहरण के लिए,

for(int x = 0; x < someMaxValue; x++)
{
    db = new DBAdapter(this);
    try
    {

        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }
    db.close();
}

के रूप में करने की अपील की:

db = new DBAdapter(this);
for(int x = 0; x < someMaxValue; x++)
{

    try
    {
        // ask the database manager to add a row given the two strings
        db.addRow
        (
                NamesStringArray[i].toString(), 
                StartTimeStringArray[i].toString(),
                EndTimeStringArray[i].toString()
        );

    }
    catch (Exception e)
    {
        Log.e("Add Error", e.toString());
        e.printStackTrace();
    }

}
db.close();

हर बार एक नया डीबीएडैप्टर बनाने से लूप की पुनरावृत्ति ही एकमात्र तरीका था जिससे मैं अपने हेल्पर वर्ग के माध्यम से एक डेटाबेस में अपने तार प्राप्त कर सकता था।


4

SQLiteDatabase API की मेरी समझ यह है कि यदि आपके पास एक बहु थ्रेडेड एप्लिकेशन है, तो आप एक एकल डेटाबेस की ओर इशारा करते हुए 1 SQLiteDatabase ऑब्जेक्ट से अधिक का वहन नहीं कर सकते।

ऑब्जेक्ट निश्चित रूप से बनाया जा सकता है लेकिन आवेषण / अपडेट विफल हो जाते हैं यदि विभिन्न थ्रेड्स / प्रक्रियाएं (भी) विभिन्न SQLiteDatabase ऑब्जेक्ट्स का उपयोग करना शुरू कर देती हैं (जैसे हम JDBC कनेक्शन में कैसे उपयोग करते हैं)।

यहां एकमात्र समाधान 1 SQLiteDatabase ऑब्जेक्ट्स के साथ रहना है और जब भी एक स्टार्टट्रांसलेशन () 1 से अधिक थ्रेड में उपयोग किया जाता है, तो एंड्रॉइड विभिन्न थ्रेड्स में लॉकिंग को प्रबंधित करता है और अनन्य अपडेट एक्सेस करने के लिए एक समय में केवल 1 थ्रेड की अनुमति देता है।

इसके अलावा आप डेटाबेस से "रीड्स" कर सकते हैं और एक अलग धागे में एक ही SQLiteDatabase ऑब्जेक्ट का उपयोग कर सकते हैं (जबकि दूसरा धागा लिखता है) और डेटाबेस में भ्रष्टाचार कभी नहीं होगा "डेटाबेस पढ़ें" डेटाबेस तक डेटा नहीं पढ़ेगा " थ्रेड लिखना "डेटा को कमिट करता है, हालांकि दोनों समान SQLiteDatabase ऑब्जेक्ट का उपयोग करते हैं।

यह JDBC में कनेक्शन ऑब्जेक्ट कैसे होता है, से अलग है, यदि आप पास (उसी का उपयोग करते हैं) कनेक्शन ऑब्जेक्ट को पढ़ने और लिखने के बीच धागे के बीच होता है, तो हम संभवतः अप्रकाशित डेटा भी प्रिंट करेंगे।

अपने एंटरप्राइज़ एप्लिकेशन में, मैं सशर्त जांच का उपयोग करने की कोशिश करता हूं ताकि यूआई थ्रेड को कभी भी इंतजार न करना पड़े, जबकि बीजी धागा SQLiteDatabase ऑब्जेक्ट (विशेष रूप से) रखता है। मैं UI क्रियाओं की भविष्यवाणी करने और 'x' सेकंड के लिए चलने से BG थ्रेड को हटाने का प्रयास करता हूं। SQLiteDatabase कनेक्शन ऑब्जेक्ट्स को सौंपने का प्रबंधन करने के लिए भी प्रायोरिटी क्यू को बनाए रखा जा सकता है ताकि यूआई थ्रेड पहले मिल जाए।


और आप उस प्रायोरिटी क्यू - श्रोताओं (जो डेटाबेस ऑब्जेक्ट प्राप्त करना चाहते हैं) या SQL प्रश्नों में क्या डालते हैं?
15

मैंने प्राथमिकता कतार दृष्टिकोण का उपयोग नहीं किया है, लेकिन अनिवार्य रूप से "कॉलर" धागे।
स्वरूप

@Swaroop: PCMIIW "read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object,। यह हमेशा सच नहीं होता है, यदि आप "थ्रेड" पढ़ना शुरू करते हैं, तो "थ्रेड लिखने के बाद" आपको नया अपडेटेड डेटा नहीं मिल सकता है (सम्मिलित या अपडेट थ्रेड में लिखें)। थ्रेड को लिखना शुरू करने से पहले डेटा पढ़ सकते हैं। ऐसा इसलिए होता है क्योंकि लिखने का कार्य शुरू में अनन्य लॉक के बजाय आरक्षित लॉक को सक्षम करता है।
अमित विक्रम सिंह

4

आप नए वास्तुकला दृष्टिकोण लागू करने का प्रयास कर सकते हैं की घोषणा की गूगल आई / ओ 2017 में।

इसमें नई ORM लाइब्रेरी भी शामिल है, जिसे रूम कहा जाता है

इसमें तीन मुख्य घटक होते हैं: @Entity, @Dao और @Database

User.java

@Entity
public class User {
  @PrimaryKey
  private int uid;

  @ColumnInfo(name = "first_name")
  private String firstName;

  @ColumnInfo(name = "last_name")
  private String lastName;

  // Getters and setters are ignored for brevity,
  // but they're required for Room to work.
}

UserDao.java

@Dao
public interface UserDao {
  @Query("SELECT * FROM user")
  List<User> getAll();

  @Query("SELECT * FROM user WHERE uid IN (:userIds)")
  List<User> loadAllByIds(int[] userIds);

  @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
       + "last_name LIKE :last LIMIT 1")
  User findByName(String first, String last);

  @Insert
  void insertAll(User... users);

  @Delete
  void delete(User user);
}

AppDatabase.java

@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
  public abstract UserDao userDao();
}

मैं कई एन-टू-एन रिश्तों के साथ एक डेटाबेस के लिए कमरे का सुझाव नहीं देता, क्योंकि यह इस दृष्टिकोण को अच्छी तरह से संभाल नहीं करता है और आपको इन संबंधों के लिए वर्कअराउंड खोजने के लिए बहुत सारे कोड लिखना होगा।
एलेक्सपैड

3

कुछ मुद्दे थे, मुझे लगता है कि मैं समझ गया हूं कि मैं गलत क्यों हो रहा हूं।

मैंने एक डेटाबेस रैपर क्लास लिखी थी, जिसमें एक close()हेल्पर को आईने के रूप में बंद किया गया था open(), जिसे getWriteableDatabase कहा जाता था और फिर a में माइग्रेट किया गया ContentProvider। के लिए मॉडल का ContentProviderउपयोग नहीं करता है SQLiteDatabase.close()जो मुझे लगता है कि एक बड़ा सुराग है जैसा कि कोड का उपयोग करता है getWriteableDatabaseकुछ उदाहरणों में मैं अभी भी प्रत्यक्ष एक्सेस (मुख्य में स्क्रीन सत्यापन क्वेरी) कर रहा था इसलिए मैं एक getWriteableDatabase / rawQuery मॉडल में स्थानांतरित हो गया।

मैं एक सिंगलटन का उपयोग करता हूं और करीबी प्रलेखन में थोड़ी अशुभ टिप्पणी है

किसी भी खुले डेटाबेस ऑब्जेक्ट को बंद करें

(मेरी तह)।

इसलिए मेरे पास रुक-रुक कर दुर्घटनाएँ हुई हैं जहाँ मैं डेटाबेस तक पहुँचने के लिए बैकग्राउंड थ्रेड्स का उपयोग करता हूँ और वे उसी समय अग्रभूमि के रूप में चलते हैं।

इसलिए मुझे लगता है close()कि डेटाबेस को संदर्भों को पकड़े हुए किसी भी अन्य धागे की परवाह किए बिना बंद करने के लिए मजबूर करता है - इसलिए close()यह केवल मिलान को पूर्ववत नहीं कर रहा है, getWriteableDatabaseलेकिन किसी भी खुले अनुरोध को बंद करने के लिए मजबूर करता है। अधिकांश समय यह एक समस्या नहीं है क्योंकि कोड एकल थ्रेडिंग है, लेकिन बहु-थ्रेडेड मामलों में हमेशा सिंक से खुलने और बंद होने की संभावना होती है।

अन्यत्र टिप्पणियों को पढ़ने के बाद, जो बताती है कि SqLiteDatabaseHelper कोड उदाहरण गिना जाता है, तब केवल एक बार जब आप एक करीबी चाहते हैं, जहां आप एक बैकअप कॉपी करना चाहते हैं, और आप सभी कनेक्शनों को बंद करने के लिए बाध्य करना चाहते हैं और SqLite को बाध्य करना चाहते हैं। किसी भी कैश्ड सामान को लिख लें, जिसके बारे में समझ हो सकती है - दूसरे शब्दों में सभी एप्लिकेशन डेटाबेस गतिविधि को रोक दें, बस उसी स्थिति में बंद करें जब हेल्पर ने ट्रैक खो दिया है, किसी भी फ़ाइल स्तर की गतिविधि (बैकअप / पुनर्स्थापना) करें, फिर सभी को फिर से शुरू करें।

यद्यपि यह नियंत्रित फैशन में प्रयास करने और बंद करने के लिए एक अच्छे विचार की तरह लगता है, वास्तविकता यह है कि एंड्रॉइड आपके वीएम को रद्दी करने का अधिकार रखता है इसलिए किसी भी समापन को कैश किए गए अपडेट के लिखे जाने के जोखिम को कम कर रहा है, लेकिन डिवाइस की गारंटी नहीं दी जा सकती है पर बल दिया जाता है, और यदि आपने अपने कर्सर और डेटाबेस के संदर्भ (जो स्थिर सदस्य नहीं होने चाहिए) को सही ढंग से मुक्त कर दिया है, तो सहायक ने डेटाबेस को वैसे भी बंद कर दिया होगा।

तो मेरा लेना यह है कि दृष्टिकोण है:

एक सिंगलटन रैपर से खोलने के लिए getWriteableDatabase का उपयोग करें। (मैं एक संदर्भ की आवश्यकता को हल करने के लिए एक स्थिर से आवेदन संदर्भ प्रदान करने के लिए एक व्युत्पन्न अनुप्रयोग वर्ग का इस्तेमाल किया)।

कभी भी सीधे कॉल बंद न करें।

परिणामी डेटाबेस को कभी भी किसी ऐसे ऑब्जेक्ट में स्टोर न करें, जिसमें स्पष्ट गुंजाइश न हो और एक अंतर्निहित करीब () को ट्रिगर करने के लिए संदर्भ गिनती पर भरोसा करें।

यदि फ़ाइल स्तर की हैंडलिंग कर रहे हैं, तो सभी डेटाबेस गतिविधि को रोकें और फिर कॉल करें बस इस स्थिति में कि इस धारणा पर एक भगोड़ा धागा है कि आप उचित लेनदेन लिखते हैं, इसलिए भगोड़ा धागा विफल हो जाएगा और बंद डेटाबेस में कम से कम उचित लेनदेन होगा संभावित रूप से एक आंशिक लेनदेन की एक फ़ाइल स्तर की प्रतिलिपि।


0

मुझे पता है कि प्रतिक्रिया देर से है, लेकिन एंड्रॉइड में sqlite प्रश्नों को निष्पादित करने का सबसे अच्छा तरीका एक कस्टम सामग्री प्रदाता के माध्यम से है। इस तरह यूआई को डेटाबेस वर्ग (SQLiteOpenHelper वर्ग का विस्तार करने वाला वर्ग) के साथ डिकॉय किया जाता है। साथ ही प्रश्नों को एक पृष्ठभूमि थ्रेड (कर्सर लोडर) में निष्पादित किया जाता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.