How to manage a SQLite connection between multiple concurrent threads?

10

So I'm having some problems with my connection to SQLite, where I'm not finding a workaround.

Scenery

My scenario is as follows:

  • I synchronize the data of my application with a legacy system through a WebService;
  • Synchronization is done through a Thread that checks what is new in the legacy database and initiates several other Threads to retrieve the data and saves it to its local base (SQLite);
  • For each entity that finds new data in the legacy database, a new Thread is opened, (each entity has an EndPoint, where it is accessed, downloaded, saved, and then terminated by Thread). If there are new clients in the legacy base, a request is initiated that will fetch all new clients, and save them to my SQLite client table, this would run on a Thread;

Problem

The problem is that even though I use AsyncTask.SERIAL_EXECUTOR which only runs one Thread at a time, between one opening and another connection it ends up generating Lock problem, where I can not get the current connection reference and when trying to create a new one it accuses that the db is Locked .

I believe that my problem is neither with the threads running concurrently, but in my% 2 of connection management, which I am currently using is:

public abstract class GConexaoAdapter {

private final DatabaseHelper mDbHelper;
private SQLiteDatabase mDb;
protected final Context ctx;

public GConexaoAdapter(Context ctx, String dbName, int dbVersion) {
    this.mDbHelper = new DatabaseHelper(ctx, dbName, dbVersion);
    this.ctx = ctx;
}

private void open() {
    try {
        synchronized (ctx) {
            synchronized (mDbHelper) {
                if (mDb == null) {
                    mDb = mDbHelper.getWritableDatabase();
                } else {
                    synchronized (mDb) {
                        mDb = mDbHelper.getWritableDatabase();
                    }
                }
            }
        }
    } catch (IllegalStateException e) {
        e.printStackTrace();
        if (mDb == null || !mDb.isOpen())
            mDb = ctx.openOrCreateDatabase(mDbHelper.getDatabaseName(), 0,
                    null);
        Log.i("TRATAR", "tratar");
    }
}

protected SQLiteDatabase getConexao() {
    synchronized (ctx) {
        synchronized (mDbHelper) {
            if (mDb == null) {
                this.open();
            } else if (!mDb.isOpen()) {
                synchronized (mDb) {
                    if (!mDb.isOpen()) {
                        this.open();
                    }
                }
            }
            return mDb;
        }
    }
}

protected void closeConexao() {
    synchronized (ctx) {
        synchronized (mDbHelper) {
            if (mDb != null && mDb.isOpen()) {
                synchronized (mDb) {
                    if (mDb.isOpen()) {
                        if (mDb.inTransaction()) {
                            mDb.endTransaction();
                        }
                        mDb.close();
                    }
                }
            }
        }
    }
}

private static class DatabaseHelper extends SQLiteOpenHelper {

    @Override
    public void onOpen(SQLiteDatabase db) {
        super.onOpen(db);
        if (!db.isReadOnly()) {
            // db.execSQL("PRAGMA foreign_keys=ON;");
        }
    }

    DatabaseHelper(Context context, String dbNome, int dbVersion) {
        super(context, dbNome, null, dbVersion);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

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

I think I do not even need all the class I have, I tried several solutions that I found and none of them brought me the expected result.

Question?

I believe there's even a Cake recipe for this type of connection management. Because I should not be the only one in need of a solution as it is.

So how would you manage to efficiently manage my connection to the SQLite database?

Very important remark:

  

I would like to manage the SQLite connection between multiple concurrent threads, but without causing locks that end up generating exceptions and violating data integrity !

    
asked by anonymous 20.02.2014 / 14:35

5 answers

1

So after a little more research, and following some of the paths you pointed out, I came up with a Cake Recipe * as I was looking for.

I'll show you the solution that met my needs:

I followed the tips pointed out in this link , making some modifications to my needs.

public class DatabaseManager {
    private AtomicInteger mOpenCounter = new AtomicInteger();

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

    private DatabaseManager() {
        super();
    }

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

public static synchronized DatabaseManager getInstance(Context context,
        String dbName, int dbVersion) {
    if (instance == null) {
        initializeInstance(new DatabaseHelper(context, dbName, dbVersion));
        return instance;
    }

    return instance;
}

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

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

    }
}
}

And in my old class of control I made some modifications that only apply to adapt my old class to the new class of connection management:

public abstract class GConexaoAdapter {

protected final Context ctx;
private final String dbName;
private final int dbVersion;

public GConexaoAdapter(Context ctx, String dbName, int dbVersion) {
    this.dbName = dbName;
    this.dbVersion = dbVersion;
    this.ctx = ctx;
}

protected SQLiteDatabase getConexao() {
    return DatabaseManager.getInstance(ctx, dbName, dbVersion)
            .openDatabase();
}

protected void closeConexao() {
    DatabaseManager.getInstance(ctx, dbName, dbVersion).closeDatabase();
}
}

This second class is not required you can use DatabaseManager directly in this way:

private long create(ContentValues valores) {
    try {
        DatabaseManager.getInstance(ctx, "db", 1).openDatabase()
                .beginTransaction();
        long result = DatabaseManager.getInstance(ctx, "db", 1).openDatabase().insert(nomeTabela, null, valores);
        DatabaseManager.getInstance(ctx, "db", 1).openDatabase().setTransactionSuccessful();
        return result;
    } finally {
        DatabaseManager.getInstance(ctx, "db", 1).openDatabase().endTransaction();
        DatabaseManager.getInstance(ctx, "db", 1).closeDatabase();
    }
}

Or adapt it to the way you work.

* ¹It is not a cake recipe as actually expected by the question, but for my specific case and I believe that for others too, it will be very useful, and can be easily adapted to your environment if you are working in a modular way and organized.

  

Note: This solution manages the SQLite connection in thread-safe

    
20.02.2014 / 18:04
3

Synchronizing the bank's opening, reading / writing, and closing methods individually does not solve an essential problem that is the need for access to the bank to be atomic.

It would make your life easier if instead of using that lot of synchronized within that class (sync in a Context ??) you used within the doInBackground() method of your AsyncTask s, wrapping within a block synchronized an atomic operation in the bank composed of open() , read or write, close() :

doInBackground() {

    //Traz os dados do webservice;

    synchronized(objetoLock) {
        open();
        //lê ou grava no banco;
        close();
    }
}
I also suggest that you use as a lock of the synchronized blocks an object that lasts throughout the entire application, I would make use of a unique object for this, but you can use a benchmarked object (for example the instance of GConexaoAdapter ) as long as it lasts long enough in your application to be used by all the bank operations that occur. And it does not cost to write, the synchronization has to be done always in the same object, otherwise it gives concurrent access problem in the same way.

    
20.02.2014 / 15:16
1

I strongly recommend using an external queue (there are several solutions ready for this) for continuous insertion ... and separate a single thread to consume it function ... will not generate lock and will guarantee the chronological integrity of the data .. just will not be synchronous with reading the data in the original database ..

    
20.02.2014 / 20:45
0

Start with the default "who opens the connection is who closes it", I'm sorry not to know if there is a cool name for this pattern ... :-)

Implementing the DataSource interface helps you define how you will work with the single connection in the database.

I think this might be a path ... :-)

    
20.02.2014 / 14:47
0

I do not understand a lot of android, but we have a desktop application that also uses SQLite and the solution for using it in multiple threads was to create a class to be a Pool of connections and this pool had only 1 connection for writing and 2 for reading

    
20.02.2014 / 15:32