অ্যান্ড্রয়েডে এসকিউএলাইটের জন্য সেরা অনুশীলনগুলি কী কী?


694

অ্যান্ড্রয়েড অ্যাপের মধ্যে এসকিউএলাইট ডাটাবেসে কোয়েরি চালানোর সময় সেরা অনুশীলনগুলি কী বলে বিবেচিত হবে?

AsyncTask এর doInBackground থেকে অনুসন্ধানগুলি সরিয়ে, মুছতে এবং নির্বাচন করা কি নিরাপদ? অথবা আমি ইউআই থ্রেড ব্যবহার করা উচিত? আমি মনে করি যে ডাটাবেস ক্যোয়ারীগুলি "ভারী" হতে পারে এবং ইউআই থ্রেডটি ব্যবহার করা উচিত নয় কারণ এটি অ্যাপ্লিকেশনটিকে লক করতে পারে - এর ফলে একটি অ্যাপ্লিকেশন নট রেসপন্সিং (এএনআর) আসে।

আমার যদি বেশ কয়েকটি এসিঙ্কটাস্ক থাকে তবে তাদের কি সংযোগ ভাগ করা উচিত বা তাদের প্রতিটি সংযোগ খোলা উচিত?

এই পরিস্থিতিতে জন্য কোন সেরা অনুশীলন আছে?


10
আপনি যাই করুন না কেন, যদি আপনার সামগ্রী সরবরাহকারী (বা এসকিউএলাইট ইন্টারফেস) প্রকাশ্যে মুখোমুখি হয় তবে আপনার ইনপুটগুলি স্যানিটাইজ করতে ভুলবেন না!
ক্রিস্টোফার মিকিনস্কি

37
আপনার অবশ্যই ইউআই থ্রেড থেকে ডিবি অ্যাক্সেস করা উচিত নয়, আমি আপনাকে এটি অনেক কিছু বলতে পারি।
এডওয়ার্ড ফালক

নিবন্ধন করুন নিশ্চয়ই এমন ব্যবহারের কেস রয়েছে যেখানে এটি করা বৈধ?
মাইকেল

4
আপনি যদি ইউআই থ্রেড থেকে কোনও আই / ও, নেটওয়ার্ক অ্যাক্সেস ইত্যাদি করেন তবে অপারেশন শেষ না হওয়া পর্যন্ত পুরো ডিভাইস হিম হয়ে যায়। এটি যদি 1/20 সেকেন্ডে পূর্ণ হয় তবে জরিমানা। যদি এটি বেশি সময় নেয় তবে আপনার ব্যবহারকারীর একটি খারাপ অভিজ্ঞতা আছে।
এডওয়ার্ড ফ্যালক

উত্তর:


632

সন্নিবেশ, আপডেটগুলি, মুছে ফেলা এবং পড়াগুলি একাধিক থ্রেড থেকে সাধারণত ঠিক থাকে তবে ব্র্যাডের উত্তর সঠিক নয়। আপনি কীভাবে আপনার সংযোগগুলি তৈরি করেন এবং সেগুলি ব্যবহার করেন সে সম্পর্কে আপনাকে সতর্কতা অবলম্বন করতে হবে। এমন পরিস্থিতিতে রয়েছে যেখানে আপনার আপডেট কলগুলি ব্যর্থ হবে, এমনকি যদি আপনার ডাটাবেসটি ক্ষতিগ্রস্থ না হয়।

প্রাথমিক উত্তর।

স্ক্লাইটঅপেনহেল্পার অবজেক্টটি একটি ডাটাবেস সংযোগ ধরে। এটি আপনাকে একটি পড়ার এবং লেখার সংযোগের প্রস্তাব দেয় বলে মনে হয়, তবে এটি সত্যিই তা করে না। কেবল পঠনযোগ্যকে কল করুন এবং আপনি লেখার ডাটাবেস সংযোগ নির্বিশেষে পাবেন।

সুতরাং, একটি সহায়ক উদাহরণ, একটি ডিবি সংযোগ। এমনকি আপনি যদি একাধিক থ্রেড থেকে এটি ব্যবহার করেন তবে একবারে একটি সংযোগ। স্ক্লাইটডাটাবেস অবজেক্ট অ্যাক্সেসকে সিরিয়ালযুক্ত রাখতে জাভা লক ব্যবহার করে। সুতরাং, যদি 100 থ্রেডের একটি ডিবি উদাহরণ থাকে তবে প্রকৃত অন ডিস্ক ডাটাবেসে কলগুলি সিরিয়ালযুক্ত হয়।

সুতরাং, একটি সহায়ক, এক ডিবি সংযোগ, যা জাভা কোডে সিরিয়ালযুক্ত। একটি থ্রেড, 1000 থ্রেড, আপনি যদি তাদের মধ্যে ভাগ করা কোনও সহায়ক উদাহরণ ব্যবহার করেন তবে আপনার সমস্ত ডিবি অ্যাক্সেস কোডটি সিরিয়াল। এবং জীবন ভাল (ish)।

আপনি যদি একই সময়ে প্রকৃত স্বতন্ত্র সংযোগগুলি থেকে ডাটাবেসে লেখার চেষ্টা করেন তবে একটি ব্যর্থ হবে। প্রথমটি সম্পন্ন হওয়া এবং তারপরে লেখার আগে পর্যন্ত এটি অপেক্ষা করবে না। এটি কেবল আপনার পরিবর্তনটি লিখবে না। সবচেয়ে খারাপ, আপনি যদি এসকিউএলডিটাবেসে সন্নিবেশ / আপডেটের সঠিক সংস্করণটি কল না করেন তবে আপনি কোনও ব্যতিক্রম পাবেন না। আপনি কেবল আপনার লগকেটে একটি বার্তা পাবেন এবং এটি হবে।

সুতরাং, একাধিক থ্রেড? একজন সহায়ক ব্যবহার করুন। সময়কাল। আপনি যদি কেবল একটি থ্রেড লিখতে জানেন তবে আপনি একাধিক সংযোগ ব্যবহার করতে পারবেন এবং আপনার পড়া দ্রুত হবে তবে ক্রেতা সাবধান থাকবেন। আমি এতটা পরীক্ষা করিনি।

এখানে আরও বিস্তারিত এবং উদাহরণ অ্যাপ সহ একটি ব্লগ পোস্ট।

ধূসর এবং আমি প্রকৃতপক্ষে একটি ওরম টুলটি গুছিয়ে নিচ্ছি, তার অরমলাইটের ভিত্তিতে, যা অ্যান্ড্রয়েড ডাটাবেস বাস্তবায়নের সাথে নেটিভভাবে কাজ করে এবং ব্লগ পোস্টে বর্ণিত নিরাপদ সৃষ্টি / কলিং কাঠামো অনুসরণ করে। খুব তাড়াতাড়ি বাইরে বের করা উচিত। এটা দেখ.


ইতিমধ্যে, একটি ফলোআপ ব্লগ পোস্ট রয়েছে:

পূর্বে উল্লিখিত লকিং উদাহরণের 2 পয়েন্ট 0 দিয়ে কাঁটাচামচটি চেকআউট করুন :


2
অন্যদিকে, Ormlite এর অ্যান্ড্রয়েড সমর্থন ormlite.sourceforge.net/sqlite_java_android_orm.html পাওয়া যাবে । এখানে নমুনা প্রকল্প, ডকুমেন্টেশন এবং জার রয়েছে।
ধূসর

1
এক সেকেন্ড একদিকে। Ormlite কোডটিতে সহায়ক ক্লাস রয়েছে যা dbhelper দৃষ্টান্ত পরিচালনা করতে ব্যবহার করা যেতে পারে। আপনি ormlite স্টাফ ব্যবহার করতে পারেন, তবে এটির প্রয়োজন নেই। আপনি কেবল সংযোগ ব্যবস্থাপনার জন্য সহায়ক ক্লাসগুলি ব্যবহার করতে পারেন।
কেভিন গ্যালিগান

31
কাজি, বিস্তারিত ব্যাখ্যার জন্য আপনাকে ধন্যবাদ। আপনি কি একটি বিষয় পরিষ্কার করতে পারেন - আমি বুঝতে পেরেছি যে আপনার এক জন সহায়ক থাকা উচিত, তবে আপনারও কেবল একটি সংযোগ থাকা উচিত (যেমন একটি স্ক্লাইট ডাটাবেস অবজেক্ট)? অন্য কথায়, আপনি কতবার getWritableDatabase কল করবেন? এবং সমানভাবে গুরুত্বপূর্ণ আপনি যখন কল ()?
আর্টেম

3
আমি কোড আপডেট করেছি। আমি যখন ব্লগ হোস্টগুলি স্যুইচ করেছিলাম তখন মূলটি হারিয়ে গিয়েছিল, তবে আমি কিছু স্লিমড ডাউন কোড কোড যুক্ত করেছি যা সমস্যাটি প্রদর্শন করবে। এছাড়াও, আপনি কীভাবে একক সংযোগ পরিচালনা করবেন? আমার শুরুতে অনেক জটিল সমাধান ছিল, তবে আমি তখন থেকে এটি সংশোধন করেছি। এখানে একবার দেখুন: টাচলাব.কম
কেভিন

সংযোগ নিষ্পত্তি করার প্রয়োজন আছে এবং এটি কোথায় করবেন?
10:58

187

একযোগে ডেটাবেস অ্যাক্সেস

আমার ব্লগে একই নিবন্ধ (আমি আরও ফর্ম্যাটিং পছন্দ করি)

আমি একটি ছোট্ট নিবন্ধ লিখেছিলাম যা আপনার অ্যান্ড্রয়েড ডাটাবেস থ্রেডটিতে কীভাবে অ্যাক্সেস রাখবে তা বর্ণনা করে।


ধরে নিচ্ছি আপনার নিজের 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)

এটি ঘটছে কারণ প্রতিবার আপনি নতুন এসকিউএলইপেনহেল্পার বস্তুটি তৈরি করার সময় আপনি আসলে নতুন ডাটাবেস সংযোগ তৈরি করছেন making আপনি যদি একই সময়ে প্রকৃত স্বতন্ত্র সংযোগগুলি থেকে ডাটাবেসে লেখার চেষ্টা করেন তবে একটি ব্যর্থ হবে। (উপরের উত্তর থেকে)

একাধিক থ্রেড সহ ডাটাবেস ব্যবহার করতে আমাদের এটি নিশ্চিত করতে হবে যে আমরা একটি ডাটাবেস সংযোগ ব্যবহার করছি।

আসুন সিঙ্গলটন ক্লাসের ডেটাবেস ম্যানেজার তৈরি করি যা একক 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

যেহেতু আমরা কেবল একটি ডেটাবেস সংযোগ ব্যবহার করছি, পদ্ধতি getDatbox () থ্রেডড 1 এবং থ্রেড 2 এর জন্য এসকিউএলডিটি ডাটাবেস অবজেক্টের একই উদাহরণ দেয় । কি ঘটছে, থ্রেড 1 ডাটাবেস বন্ধ করতে পারে, যখন থ্রেডড 2 এখনও এটি ব্যবহার করছে। এজন্য আমাদের ইলিজালস্টেট এক্সপশন ক্র্যাশ হয়েছে।

আমাদের নিশ্চিত করতে হবে যে কেউ ডেটাবেস ব্যবহার করছে না এবং কেবল তখনই এটি বন্ধ করে দেওয়া উচিত। স্ট্যাকোভ্লোতে থাকা কিছু লোক আপনার এসকিউএলডিড ডাটাবেসটি কখনই বন্ধ না করার পরামর্শ দেয় । এর ফলে নিম্নলিখিত লগক্যাট বার্তাটি আসবে।

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 বর্গ। এই পদ্ধতির অভ্যন্তরে, আমাদের একটি কাউন্টার রয়েছে, যা নির্দেশ করে যে কতবার ডাটাবেস খোলা হয়। যদি এটির সমান হয়, এর অর্থ আমাদের নতুন ডাটাবেস সংযোগ তৈরি করতে হবে, যদি না হয় তবে ডেটাবেস সংযোগ ইতিমধ্যে তৈরি করা হয়েছে।

ক্লোজড্যাটাবেস () পদ্ধতিতে একই ঘটে । যতবার আমরা এই পদ্ধতিটি কল করি, পাল্টা হ্রাস হয়, যখনই এটি শূন্য হয়, আমরা ডাটাবেস সংযোগটি বন্ধ করে দিই।


এখন আপনার আপনার ডাটাবেস ব্যবহার করতে সক্ষম হওয়া উচিত এবং নিশ্চিত হওয়া উচিত যে এটি থ্রেডটি নিরাপদ।


10
আমি সেই লোকদের একজন যারা আপনাকে পরামর্শ দেয় কখনই ডিবি বন্ধ করবেন না। আপনি যদি ডিবি খোলেন তবে এটি বন্ধ করবেন না, তবে কেবল আবার খোলার চেষ্টা করুন আপনি কেবল "ফুটো পাওয়া" ত্রুটি পাবেন। আপনি যদি কেবলমাত্র একটি একক উন্মুক্ত সহায়ক ব্যবহার করেন এবং কখনই ডিবি বন্ধ করেন না, আপনি ত্রুটিটি পাবেন না। যদি আপনি অন্যথায় সন্ধান করেন তবে দয়া করে আমাকে জানান (কোড সহ)। আমার এটি সম্পর্কে কোথাও দীর্ঘ পোস্ট ছিল, তবে এটি খুঁজে পাচ্ছি না। প্রশ্ন এখানে commonsware দ্বারা বললেন, আমাদের উভয় তাই পয়েন্ট ডিপার্টমেন্টে outguns ধরনের যারা: stackoverflow.com/questions/7211941/...
কেভিন Galligan

4
আরও চিন্তা। # 1, আমি আপনার ম্যানেজারের ভিতরে সহায়ক তৈরি করব। সমস্যার বাইরে থাকার জন্য জিজ্ঞাসা করছেন। নতুন দেব কোনও পাগল কারণে সরাসরি সাহায্যকারীকে কল করতে পারে। এছাড়াও, যদি আপনার কোনও ডিআইডি পদ্ধতি প্রয়োজন হয় তবে উদাহরণটি ইতিমধ্যে উপস্থিত থাকলে একটি ব্যতিক্রম নিক্ষেপ করুন। মাল্টি-ডিবি অ্যাপস স্পষ্টতই ব্যর্থ হবে will # 2, এমডিটাবেস ক্ষেত্র কেন? এটি সহায়ক থেকে পাওয়া যায়। # 3, "কখনই বন্ধ হবে না" এর দিকে আপনার প্রথম পদক্ষেপ হিসাবে, যখন আপনার অ্যাপ্লিকেশনটি ক্র্যাশ হয়ে যায় এবং "বন্ধ" হয় না তখন আপনার ডিবিতে কী ঘটে? ইঙ্গিত, কিছুই না। এটি সূক্ষ্ম, কারণ এসকিউএলাইট অত্যন্ত স্থিতিশীল। আপনার কেন এটি বন্ধ করার দরকার নেই তা নির্ধারণের জন্য এটি ছিল পদক্ষেপ 1 ।
কেভিন গ্যালিগান

1
উদাহরণ পাওয়ার আগে আপনি কল করার জন্য কোনও সার্বজনীন পদ্ধতি ব্যবহার করার কোনও কারণ? কেন একটি ব্যক্তিগত নির্মাণকারী বলা হয় না if(instance==null)? আপনি প্রতিবার আরম্ভ কল কল ছাড়া কোন উপায় ছেড়ে; অন্য অ্যাপ্লিকেশন ইত্যাদিতে এটি আরম্ভ করা হয়েছে কিনা তা আপনি কীভাবে জানবেন?
চিফ টুপেনসিলস

1
initializeInstance()টাইপের একটি প্যারামিটার রয়েছে SQLiteOpenHelperতবে আপনার মন্তব্যে আপনি ব্যবহার করার কথা উল্লেখ করেছেন DatabaseManager.initializeInstance(getApplicationContext());। কি হচ্ছে? এটি কীভাবে সম্ভব কাজ করতে পারে?
ফিজাল

2
@ ডিমেট্রোডানাইলিক "ডেটাবেস ম্যানেজারটি থ্রেড সেফ সিঙ্গলটন, সুতরাং এটি যে কোনও জায়গায় ব্যবহার করা যেতে পারে" যা বিরসিরের প্রশ্নের জবাবে সত্য নয়। অবজেক্টগুলি ভাগ করা ক্রস প্রক্রিয়া নয়। আপনার ডেটাবেস
ম্যানেজারের

17
  • দীর্ঘ চলমান ক্রিয়াকলাপের জন্য একটি Threadবা ব্যবহার করুন AsyncTask(50ms +)। আপনার অ্যাপটি কোথায় তা দেখতে পরীক্ষা করুন। বেশিরভাগ অপারেশন (সম্ভবত) কোনও থ্রেডের প্রয়োজন হয় না, কারণ বেশিরভাগ অপারেশন (সম্ভবত) কেবল কয়েকটি সারি জড়িত। বাল্ক অপারেশনের জন্য একটি থ্রেড ব্যবহার করুন।
  • SQLiteDatabaseথ্রেডগুলির মধ্যে ডিস্কে প্রতিটি ডিবির জন্য একটি উদাহরণ ভাগ করুন এবং খোলা সংযোগগুলি ট্র্যাক রাখতে একটি গণনা ব্যবস্থা প্রয়োগ করুন।

এই পরিস্থিতিতে জন্য কোন সেরা অনুশীলন আছে?

আপনার সমস্ত শ্রেণীর মধ্যে একটি স্থির ক্ষেত্র ভাগ করুন। আমি ও অন্যান্য জিনিস যা ভাগ করে নেওয়া প্রয়োজন তার জন্য একটি সিঙ্গেলটন প্রায় রাখতাম। আপনি কখনই ডাটাবেসটি তাড়াতাড়ি বন্ধ না করেন বা এটিকে খোলা না রাখেন তা নিশ্চিত করার জন্য একটি গণনা স্কিম (সাধারণত অ্যাটমিকআইন্টিজার ব্যবহার করে) ব্যবহার করা উচিত।

আমার সমাধান:

সর্বাধিক বর্তমান সংস্করণের জন্য, https://github.com/JakarCo/datediamanager দেখুন তবে আমি কোডটি এখানেও আপডেট রাখার চেষ্টা করব। আপনি যদি আমার সমাধানটি বুঝতে চান তবে কোডটি দেখুন এবং আমার নোটগুলি পড়ুন। আমার নোটগুলি সাধারণত বেশ সহায়ক হয়।

  1. নামের একটি নতুন ফাইলে কোডটি অনুলিপি / পেস্ট করুন DatabaseManager। (বা এটি গিথুব থেকে ডাউনলোড করুন)
  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
আপনি যখন "ক্লোজ" বলুন এবং তারপরে ক্লাসটি পুনরায় ব্যবহার করার চেষ্টা করবেন তখন কি হবে? এটা কি ক্রাশ হবে? বা এটি আবার ডিবি ব্যবহার করতে সক্ষম হতে স্বয়ংক্রিয়ভাবে পুনরায় আরম্ভ করবে?
অ্যান্ড্রয়েড বিকাশকারী

1
@ অ্যান্ড্রয়েড ডেভেলপার, আপনি যদি কল করেন তবে ক্লাসের একই উদাহরণ ব্যবহার করার আগে closeআপনাকে openআবার কল করতে হবে অথবা আপনি একটি নতুন উদাহরণ তৈরি করতে পারেন। closeকোডটি যেহেতু আমি সেট করেছি db=null, আপনি তার থেকে রিটার্নের মানটি ব্যবহার করতে পারবেন না getDb(যেহেতু এটি নাল হবে), তাই আপনি NullPointerExceptionযদি কিছু করেন তবে আপনি পেয়ে যাবেনmyInstance.close(); myInstance.getDb().query(...);
রেড

একত্রিত getDb()এবং open()একক পদ্ধতিতে কেন নয় ?
অ্যালেক্স বারডুসেল

@ বারডু, ডাটাবেস কাউন্টার এবং খারাপ ডিজাইনের উপর অতিরিক্ত নিয়ন্ত্রণ সরবরাহের একটি মিশ্রণ। যদিও এটি করার সর্বোত্তম উপায় অবশ্যই নয়। আমি কয়েক দিনের মধ্যে এটি আপডেট করব।
রিড

@ বারডু, আমি সবেমাত্র এটি আপডেট করেছি। আপনি এর থেকে নতুন কোড পেতে পারেন এখানে । আমি এটি পরীক্ষা করেছি না, সুতরাং আমার যদি পরিবর্তনগুলি করা হয় তবে দয়া করে আমাকে জানান।
রিড

11

মাল্টি-থ্রেডিংয়ের সাথে ডাটাবেসটি খুব নমনীয়। আমার অ্যাপস একযোগে বিভিন্ন থ্রেড থেকে তাদের ডিবিগুলিকে আঘাত করেছে এবং এটি ঠিক আছে। কিছু ক্ষেত্রে আমার একাধিক প্রক্রিয়া একই সাথে ডিবিতে হিট হয় এবং এটি খুব ভাল কাজ করে।

আপনার অ্যাসিঙ্ক কার্যগুলি - আপনি যখন পারেন তখন একই সংযোগটি ব্যবহার করুন, তবে যদি আপনাকে করতে হয় তবে বিভিন্ন কার্য থেকে ডিবিতে অ্যাক্সেস করা ঠিক আছে।


এছাড়াও, আপনার কি বিভিন্ন সংযোগে পাঠক এবং লেখক রয়েছে বা তাদের একটি সংযোগ ভাগ করা উচিত? ধন্যবাদ।
গ্রে

@ গ্রে - সঠিক, আমার এটি স্পষ্টভাবে উল্লেখ করা উচিত ছিল। সংযোগ যতদূর সম্ভব, আমি যথাসম্ভব একই সংযোগটি ব্যবহার করব, তবে যেহেতু লকিংটি ফাইল সিস্টেমের স্তরে পরিচালিত হয়, আপনি কোডে এটি একাধিকবার খুলতে পারেন, তবে আমি যতটা সম্ভব একটি একক সংযোগ ব্যবহার করব। অ্যান্ড্রয়েড স্ক্লাইট ডিবি খুব নমনীয় এবং ক্ষমাশীল।
ব্র্যাড হেইন

3
@ গ্রে, কেবল এই পদ্ধতিটি ব্যবহার করা লোকদের জন্য একটি আপডেট তথ্য পোস্ট করতে চেয়েছিলেন। ডকুমেন্টেশন বলেছেন: এই পদ্ধতিটি এখন কিছুই করে না। ব্যবহার করবেন না.
পাইজুসন

3
আমি এই পদ্ধতিটি মারাত্মকভাবে ব্যর্থ হতে দেখেছি, আমরা একাধিক অ্যাপ্লিকেশন থেকে অ্যাক্সেসের জন্য কনটেন্টপ্রোভাইডারটিতে স্যুইচ করছি। আমাদের পদ্ধতিগুলিতে আমাদের কিছুটা একযোগে করতে হবে তবে একই সাথে সমস্ত ডেটা অ্যাক্সেস করার প্রক্রিয়াগুলির সাথে কোনও সমস্যা সমাধান করা উচিত।
জেপিএম

2
আমি জানি এটি পুরানো, তবে এটি ভুল। এটা তোলে পারে বিভিন্ন থেকে অ্যাক্সেসের ডিবি ঠিক কাজ SQLiteDatabaseবিভিন্ন বস্তু AsyncTaskS / ThreadS, কিন্তু কখনও কখনও ত্রুটি হতে হবে যা কেন SQLiteDatabase (লাইন 1297) ব্যবহার করে Lockগুলি
রিড

7

Dmytro এর উত্তর আমার ক্ষেত্রে ভাল কাজ করে। আমি মনে করি ফাংশনটিকে সিঙ্ক্রোনাইজড হিসাবে ঘোষণা করা ভাল। কমপক্ষে আমার ক্ষেত্রে, এটি নাল পয়েন্টার ব্যতিক্রমকে অনুরোধ করবে অন্যথায়, যেমন getWritableDatbox এখনও একটি থ্রেডে ফিরে আসে নি এবং অন্য থ্রেডের মধ্যে ওপেনড্যাটবেস ডাকে।

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

mDatabaseHelper.getWritableDatabase (); এই নতুন ডাটাবেসের বস্তুর তৈরী করবে না
ও পার্শ্ববর্তী

5

কয়েক ঘন্টা এটির সাথে লড়াই করার পরে, আমি খুঁজে পেয়েছি যে আপনি প্রতি ডিবি এক্সিকিউশনটিতে কেবলমাত্র একটি ডিবি হেল্পার অবজেক্ট ব্যবহার করতে পারেন। উদাহরণ স্বরূপ,

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

এসকিউএলডিটাবেস এপিআইএস সম্পর্কে আমার বোঝা হ'ল আপনার যদি একাধিক থ্রেডযুক্ত অ্যাপ্লিকেশন থাকে তবে আপনার একক ডাটাবেসের দিকে নির্দেশ করে 1 টি এসকিউএলডিটাবেসস অবজেক্টের বেশি থাকতে পারে না।

অবজেক্টটি অবশ্যই তৈরি করা যেতে পারে তবে বিভিন্ন থ্রেড / প্রসেসগুলি (খুব) বিভিন্ন এসকিউএলডিটাবেসস অবজেক্ট (যেমন আমরা জেডিবিসি সংযোগে কীভাবে ব্যবহার করি) ব্যবহার শুরু করলে সন্নিবেশ / আপডেটগুলি ব্যর্থ হয়।

এখানে একমাত্র সমাধান হ'ল 1 এসকিউএলডিটি ডাটাবেস অবজেক্টের সাথে থাকা এবং যখনই কোনও স্টার্ট ট্রান্সজেকশন () 1 টিরও বেশি থ্রেডে ব্যবহৃত হয়, অ্যান্ড্রয়েড বিভিন্ন থ্রেডে লক পরিচালনা করে এবং একসাথে কেবল 1 টি থ্রেডকে একচেটিয়া আপডেট অ্যাক্সেসের অনুমতি দেয়।

এছাড়াও আপনি ডাটাবেস থেকে "রিডস" করতে পারেন এবং একই এসকিউএলডিটি ডাটাবেস অবজেক্টটি অন্য একটি থ্রেডে ব্যবহার করতে পারেন (অন্য থ্রেড লিখেছেন) এবং ডাটাবেস দুর্নীতি কখনও হবে না "ডাটা থ্রেড" ডাটাবেস থেকে ডেটা পড়বে না " থ্রেড লিখুন "উভয়ই একই এসকিউএলডিট ডাটাবেস অবজেক্ট ব্যবহার করলেও ডেটা করে।"

এটি জেডিবিসি-তে সংযোগ অবজেক্টের থেকে আলাদা যেখানে আপনি থ্রেডগুলি পড়ার এবং লেখার মধ্যে যদি সংযোগ অবজেক্টটি প্রায় পাস (একই ব্যবহার করুন) করেন তবে আমরা সম্ভবত আপত্তিহীন ডেটাও মুদ্রণ করব।

আমার এন্টারপ্রাইজ অ্যাপ্লিকেশনটিতে, আমি শর্তসাপেক্ষ চেকগুলি ব্যবহার করার চেষ্টা করি যাতে ইউআই থ্রেডটি কখনই অপেক্ষা করতে না হয়, যখন বিজি থ্রেডটি এসকিউএলডিটাবেসস অবজেক্ট (একচেটিয়াভাবে) ধারণ করে। আমি ইউআই অ্যাকশনগুলির পূর্বাভাস দেওয়ার চেষ্টা করি এবং বিজি থ্রেডকে 'x' সেকেন্ডের জন্য চালানো থেকে পিছিয়ে দেই। এছাড়াও যে কেউ এসকিউএলডিটিবেস সংযোগ অবজেক্টগুলি হস্তান্তর পরিচালনার জন্য অগ্রাধিকারের কিউইউ বজায় রাখতে পারে যাতে ইউআই থ্রেডটি এটি প্রথম পায়।


এবং সেই অগ্রাধিকারের কি আপনি রেখেছেন - শ্রোতা (যে ডাটাবেস অবজেক্ট পেতে চান) বা এসকিউএল কোয়েরি?
পাইজুসন

আমি অগ্রাধিকারের সারি পদ্ধতির ব্যবহার করি নি, তবে মূলত "কলার" থ্রেড।
স্বরূপ

@ স্বরূপ: পিসিএমআইআইডাব্লু "read thread" wouldn't read the data from the database till the "write thread" commits the data although both use the same SQLiteDatabase object,। এটি সর্বদা সত্য নয়, যদি আপনি "থ্রেড লেখার" ঠিক পরে "থ্রেড পড়ুন" শুরু করেন তবে আপনি সদ্য আপডেট হওয়া ডেটা পেতে পারেন না (লেখার থ্রেডে threadোকানো বা আপডেট করা)। থ্রেড পড়ুন থ্রেড লেখার শুরুর আগে ডেটা পড়তে পারে। এটি ঘটে কারণ লিখন অপারেশন প্রাথমিকভাবে একচেটিয়া লকের পরিবর্তে সংরক্ষিত লক সক্ষম করে।
অমিত বিক্রম সিং 14

4

আপনি নতুন স্থাপত্য পদ্ধতির প্রয়োগ করার চেষ্টা করতে পারেন anounced গুগল ইনপুট / আউটপুট 2017 এ।

এটি রুম নামে পরিচিত নতুন ওআরএম গ্রন্থাগারও অন্তর্ভুক্ত

এটিতে তিনটি প্রধান উপাদান রয়েছে: @ এন্টি, @ দাও এবং @ ডেটাবেস

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()বলা হত যা getWritableDatedia নামে পরিচিত এবং তারপরে একটিতে স্থানান্তরিত হয়েছিল ContentProvider। কোডটি ContentProviderব্যবহার না করে এর জন্য যে মডেলটি SQLiteDatabase.close()আমি মনে করি এটি একটি বড় চিহ্ন হিসাবে ব্যবহার করে না getWriteableDatabaseকিছু ক্ষেত্রে আমি এখনও সরাসরি অ্যাক্সেস করছিলাম (পর্দার বৈধতা মূলত কোয়েরিগুলি তাই আমি একটি getWritableDatabase / RawQuery মডেলটিতে স্থানান্তরিত হয়েছি)।

আমি একটি সিঙ্গলটন ব্যবহার করি এবং নিকটতম ডকুমেন্টেশনে কিছুটা অশুভ মন্তব্য রয়েছে

যে কোনও উন্মুক্ত ডাটাবেস অবজেক্টটি বন্ধ করুন

(আমার সাহসী)

সুতরাং আমার মাঝে মাঝে ক্র্যাশ হয়েছে যেখানে আমি ডেটাবেস অ্যাক্সেস করতে ব্যাকগ্রাউন্ড থ্রেড ব্যবহার করি এবং সেগুলি একইসাথে অগ্রভাগ হিসাবে চলে।

সুতরাং আমি মনে করি close()রেফারেন্স ধারণ করে অন্য কোনও থ্রেড নির্বিশেষে ডাটাবেসকে বন্ধ করতে বাধ্য করে - সুতরাং close()নিজেই কেবল ম্যাচটিকে পূর্বাবস্থায় ফেরাচ্ছে না getWriteableDatabaseতবে কোনও উন্মুক্ত অনুরোধ বন্ধ করার জন্য জোর করে । কোডটি একক থ্রেডিং হওয়ায় বেশিরভাগ সময় এটি সমস্যা হয় না তবে বহু-থ্রেডযুক্ত ক্ষেত্রে সর্বদা সিঙ্কের বাইরে খোলার এবং বন্ধ হওয়ার সম্ভাবনা থাকে।

অন্য কোথাও মন্তব্যগুলি পড়ে যা ব্যাখ্যা করে যে স্ক্যালিটি ডেটাবেসহেল্পার কোড উদাহরণটি গণনা করে, তবে কেবলমাত্র আপনি যখনই ব্যাকআপ কপিটি করতে চান সেই পরিস্থিতিটি আপনি যখন চান তখনই আপনি চান এবং আপনি সমস্ত সংযোগ বন্ধ করতে বাধ্য করতে এবং স্ক্লাইটকে বাধ্য করতে চান যে কোনও ক্যাশেড স্টাফের জন্য অপেক্ষা করা হতে পারে তা লিখে রাখুন - অন্য কথায় হেল্পার ট্র্যাক হারিয়েছে এমন ক্ষেত্রেই বন্ধ করুন, কোনও ফাইল স্তরের কার্যকলাপ করুন (ব্যাকআপ / পুনরুদ্ধার করুন) তারপরে আবার শুরু করুন all

যদিও এটি নিয়ন্ত্রিত ফ্যাশনে চেষ্টা করা এবং বন্ধ করা ভাল ধারণা বলে মনে হয়, তবে বাস্তবতা হ'ল অ্যান্ড্রয়েড আপনার ভিএম ট্র্যাশ করার অধিকার সংরক্ষণ করে তাই কোনও বন্ধ হওয়া ক্যাশেড আপডেটগুলি না লেখার ঝুঁকি হ্রাস করে, তবে ডিভাইসটি থাকলে এটির নিশ্চয়তা দেওয়া যায় না চাপ দেওয়া হয় এবং আপনি যদি সঠিকভাবে আপনার কার্সার এবং ডেটাবেসগুলির রেফারেন্সগুলি মুক্ত করে থাকেন (যা স্থির সদস্য হওয়া উচিত নয়) তবে সাহায্যকারী ডাটাবেসটি যেভাবেই বন্ধ করে দেবে।

সুতরাং আমার গ্রহণটি হল যে পদ্ধতিটি হল:

একটি সিঙ্গলটন মোড়ক থেকে খোলার জন্য getWritableDatabase ব্যবহার করুন। (আমি একটি প্রসঙ্গের প্রয়োজনীয়তা সমাধানের জন্য স্ট্যাটিক থেকে অ্যাপ্লিকেশন প্রসঙ্গটি সরবরাহ করতে একটি উদ্ভূত অ্যাপ্লিকেশন শ্রেণি ব্যবহার করেছি)।

কখনও সরাসরি কল বন্ধ করবেন না।

ফলস্বরূপ ডাটাবেস কখনই কোনও বস্তুতে সঞ্চয় করবেন না যার সুস্পষ্ট সুযোগ নেই এবং কোনও অন্তর্নিহিত ঘনিষ্ঠ () ট্রিগার করতে রেফারেন্স গণনা উপর নির্ভর করে।

ফাইল লেভেল হ্যান্ডলিংয়ের মাধ্যমে, সমস্ত ডাটাবেস ক্রিয়াকলাপ বন্ধ করে দিন এবং তারপরে ঠিকঠাক ক্ষেত্রে যদি আপনি যথাযথ লেনদেন লেখেন এই ধারণাটির উপর কোনও পালাবদল থ্রেড থাকে, তবে পলাতক থ্রেড ব্যর্থ হবে এবং বন্ধ ডাটাবেসটিতে কমপক্ষে সঠিক লেনদেন হবে আংশিক লেনদেনের একটি ফাইল স্তরের অনুলিপি থেকে বেশি।


0

আমি জানি যে প্রতিক্রিয়া দেরিতে হয়েছে, তবে অ্যান্ড্রয়েডে স্ক্লাইট কোয়েরিগুলি কার্যকর করার সর্বোত্তম উপায় হ'ল কাস্টম সামগ্রী সরবরাহকারীর মাধ্যমে। এইভাবে ইউআইটি ডেটাবেস ক্লাস (এসকিউএলইটহেনহেলপার ক্লাসটি প্রসারিত ক্লাস) দিয়ে ডিক্লুপড হয়। এছাড়াও প্রশ্নগুলি একটি ব্যাকগ্রাউন্ড থ্রেডে চালিত হয় (কার্সার লোডার)।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.