SQLite database, multithreading, Locks and account sync on android - android

I'm trying to get a pattern that doesn't fail for a multithreaded access to my sqlite database. Also, what is driving me nuts is that I can't reproduce the issue.
I have an app which uses a DB, but also Android Accounts and Android sync to sync my app's data. My guess is that when the two happen a the same time, it crashes. I'm getting a lot of errors like:
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
* android.database.sqlite.SQLiteDatabaseLockedException: error code 5: database is locked
* android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5): , while compiling: PRAGMA journal_mode
* android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 778)
* android.database.sqlite.SQLiteException: Failed to change locale for db '/data/data/net.bicou.redmine/databases/redmine.db' to 'en_US'. \n Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
Maybe not all of them are related to the same root cause, however I'm kind of lost.
What I have is:
an abstract base class, DbAdapter, that is extended by subclasses which want to manage a single table
a class that manages the SQLite database, called DbManager, which contains a Lock
Right now the users have a version of the DbManager that is not a singleton. I'm planning to make DbManager a singleton, so that all threads share the same object. This shouldn't be a problem, because as far as I have understood/seen, the background sync and app share the same process.
Here are the classes (only the relevant parts):
public abstract class DbAdapter {
Context mContext;
protected DbManager mDbManager;
SQLiteDatabase mDb;
public static final String KEY_ROWID = "_id";
public DbAdapter(final Context ctx) {
mContext = ctx;
}
public DbAdapter(final DbAdapter other) {
mContext = other.mContext;
mDb = other.mDb;
mDbManager = other.mDbManager; // removed with singleton version
}
public synchronized DbAdapter open() throws SQLException {
if (mDb != null) {
return this;
}
mDbManager = new DbManager(mContext); // currently in production
mDbManager = DbManager.instance(mContext); // currently investigating this singleton solution
try {
mDb = mDbManager.getWritableDatabase();
} catch (final SQLException e) {
L.e("Unable to open DB, trying again in 1 second", e);
try {
Thread.sleep(1000);
} catch (final InterruptedException e1) {
L.e("Could not wait 1 second " + e1);
}
mDb = mDbManager.getWritableDatabase();// This may crash
}
return this;
}
public synchronized void close() {
mDbManager.close();
mDbManager = null;
mDb = null;
}
}
A class that needs to handle a database table will extend DbAdapter, and implement methods such as select, insert, delete, etc.
Here's the DB manager:
public class DbManager extends SQLiteOpenHelper {
private static final String DB_FILE = "db";
private static final int DB_VERSION = 15;
Context mContext;
Lock mLock = new ReentrantLock();
// Currently in prod
public DbManager(final Context context) {
super(context, DB_FILE, null, DB_VERSION);
mContext = context;
}
// singleton version will make this constructor private and add:
private static DbManager mInstance;
public static synchronized DbManager instance(Context context) {
if (instance == null) {
instance = new DbManager(context);
}
return instance;
}
#Override
public SQLiteDatabase getWritableDatabase() {
mLock.lock();
return super.getWritableDatabase();
}
#Override
public void close() {
super.close();
mLock.unlock();
}
#Override
public void onCreate(final SQLiteDatabase db) {
// ...
}
#Override
public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
// ...
}
private void createTables(final SQLiteDatabase db, final String[] statements) {
for (final String sql : statements) {
try {
db.execSQL(sql);
} catch (final Exception e) {
L.e("Unable to create table: " + sql, e);
}
}
}
}
OK, now, the questions.
Is my lock properly implemented? I'm really new to this, I don't know if the ReentrantLock is a good choice, and if I'm locking/unlocking at the right moment
Are my synchronized method properly implemented? I mean, I have placed the synchronized keyword around methods that I don't want interrupted by concurrent threads. Is this right? Can you advice on my synchronized use?
How can I reproduce the issue? I have created a test that uses 3 threads that make concurrent read/write access to the DB, and use some Thread.sleep to ensure that the db open/close from each thread overlap, but it doesn't crash. This is really bugging me, I don't think there is a lot of people that have the issue, so I don't know how to reproduce.
Is my DbAdapter + DbManager technical choice a good idea? Is there a better pattern?
Is it a good idea to make DbManager a singleton?

For multiple threads accessing, it is advisable to use the singleton pattern.
Such a way, successive calls to the same database will be seamlessly serialised.
However, it's not impossible to have some NullPointerExceptions on inserts. So, to expand your "Thread.sleep" logic, you could use this code:
#Override
public SQLiteDatabase getWritableDatabase() {
while (true) {
try {
return super.getWritableDatabase();
} catch (SQLiteDatabaseLockedException e) {
System.err.println(e);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.err.println(e);
}
}
}

Related

How to manually perform checkpoint in SQLite android?

I'm trying to create a backup of my sqlite database and I want to flush the content of the WAL file in the db first.
Here is my SQLiteOpenHelper:
public class MyDBHelper extends SQLiteOpenHelper {
private Context mContext;
private static MyDBHelper mInstance = null;
private MyDBHelper(final Context context, String databaseName) {
super(new MYDB(context), databaseName, null, DATABASE_VERSION);
this.mContext = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public static MyDBHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new MyDBHelper(context, DATABASE_NAME);
}
return mInstance;
}
private void closeDataBase(Context context) {
getInstance(context).close();
}
}
Now, my understanding is that after a checkpoint is completed, the mydb.db-wal file should be empty. Is that correct?
Here is what I've tried so far:
1.
public Completable flushWalInDB() {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
getInstance(mContext).getReadableDatabase().rawQuery("pragma wal_checkpoint;", null);
}
});
}
This doesn't throw an error but doesn't seem to do anything. After running this, I physically checked my mydb.db-wal file and had the same size. I also checked the db on the device and nothing was added in the database
After some digging around I found this
[https://stackoverflow.com/a/30278485/2610933][1]
and tried this:
2.
public Completable flushWalInDB() {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
getInstance(mContext).getReadableDatabase().execSQL("pragma wal_checkpoint;");
}
});
}
When running this it throws an error:
unknown error (code 0): Queries can be performed using SQLiteDatabase query or rawQuery methods only.
And based on this answer [https://stackoverflow.com/a/19574341/2610933][1] , I also tried to VACUUM the DB but nothing seems to happen.
public Completable vacuumDb() {
return Completable.fromAction(new Action() {
#Override
public void run() throws Exception {
getInstance(mContext).getReadableDatabase().execSQL("VACUUM");
}
});
}
}
Whats is the correct way of flushing the WAL file in the DB before creating a backup?
Thank you.
PRAGMA wal_checkpoint(2) does copy all data from the WAL into the actual database file, but it does not remove the -wal file, and any concurrent connections can make new changes right afterwards.
If you want to be really sure that there is no WAL to interfere with your backup, run PRAGMA journal_mode = DELETE. (You can switch it back afterwards.)
To manually add checkpont use PRAGMA wal_checkpoint, after searching for 2 hours following code worked for me -:
SQLiteDatabase db = this.getWritableDatabase();
String query = "PRAGMA wal_checkpoint(full)";
Cursor cursor = db.rawQuery(query, null);
if (cursor != null && cursor.moveToFirst()) {
int a = cursor.getInt(0);
int b = cursor.getInt(1);
int c = cursor.getInt(2);
}
if (cursor != null) {
cursor.close();
}

Creating database using orm

I am using ORMlite database for the first time in my application. i have taken reference from a tutorial, but instead of doing all the things exactly same i am not able to resolve an error. My code is below:-
DatabaseHelper:
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String DATABASE_NAME = "qzeodemo.db";
private static final int DATABASE_VERSION = 1;
private Dao<ModifierDetails, Integer> itemsDao;
private Dao<ItemDetails, Integer> modifiersDao;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, R.raw.ormlite_config);
}
#Override
public void onCreate(SQLiteDatabase sqliteDatabase, ConnectionSource connectionSource) {
try {
// Create tables. This onCreate() method will be invoked only once of the application life time i.e. the first time when the application starts.
TableUtils.createTable(connectionSource,ItemDetails.class);
TableUtils.createTable(connectionSource,ModifierDetails.class);
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Unable to create datbases", e);
}
}
#Override
public void onUpgrade(SQLiteDatabase sqliteDatabase, ConnectionSource connectionSource, int oldVer, int newVer) {
try {
// In case of change in database of next version of application, please increase the value of DATABASE_VERSION variable, then this method will be invoked
//automatically. Developer needs to handle the upgrade logic here, i.e. create a new table or a new column to an existing table, take the backups of the
// existing database etc.
TableUtils.dropTable(connectionSource, ItemDetails.class, true);
TableUtils.dropTable(connectionSource, ModifierDetails.class, true);
onCreate(sqliteDatabase, connectionSource);
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Unable to upgrade database from version " + oldVer + " to new "
+ newVer, e);
}
}
// Create the getDao methods of all database tables to access those from android code.
// Insert, delete, read, update everything will be happened through DAOs
public Dao<ItemDetails,Integer> getItemDao() throws SQLException {
if (itemsDao == null) {
itemsDao = getDao(ItemDetails.class);
}
return itemsDao;
}
public Dao<ModifierDetails, Integer> getMofifierDao() throws SQLException {
if (modifiersDao == null) {
modifiersDao = getDao(ModifierDetails.class);
}
return modifiersDao;
}
}
The line where i am using modifiersDao = getDao(ModifierDetails.class); is giving error
Error:(76, 30) error: invalid inferred types for D; inferred type does not conform to declared bound(s)
inferred: Dao
bound(s): Dao
where D,T are type-variables:
D extends Dao declared in method getDao(Class)
T extends Object declared in method getDao(Class)
Please help :(
Your declaration is wrong above:
private Dao< ItemDetails, Integer > modifiersDao;
but getMofifierDao() returns Dao< ModifierDetails, Integer>

What is the best way of creating greenDAO DB connection only once for single run of application?

Currently I am creating the greenDAO DB connection in a class (which opens the connection in every static method) and using it wherever I need it. But I am not sure if it's the best way of doing it.
Can anyone suggest a better way of doing it?
My Code:
import com.knowlarity.sr.db.dao.DaoMaster;
import com.knowlarity.sr.db.dao.DaoMaster.DevOpenHelper;
import com.knowlarity.sr.db.dao.DaoSession;
import com.knowlarity.sr.db.dao.IEntity;
public class DbUtils {
private static Object lockCallRecord =new Object();
private DbUtils(){};
public static boolean saveEntity(Context context , IEntity entity){
boolean t=false;
DevOpenHelper helper=null;
SQLiteDatabase db=null;
DaoMaster daoMaster=null;
DaoSession daoSession =null;
try{
helper = new DaoMaster.DevOpenHelper(context, IConstant.DB_STRING, null);
db = helper.getReadableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
//Some business logic here for fetching and inserting the data.
}catch (Exception e){
Log.e("saveEntity", e.getStackTrace().toString());
}finally{
if(daoSession!=null)daoSession.clear();
daoMaster=null;
if(db.isOpen())db.close();
helper.close();
}
return t;
}
Your approach causes the database to be loaded very often which is not necessary and may slow down your app significantly.
Open the database once and store it somewhere and request it from there if needed.
Personally I use a global DaoSession and local DaoSessions. The local DaoSessions get used where nothing should remain in the session cache (i.e. persisting a new object into the database, that is likely to be used only very infrequent or performing some queries which will load a lot of entities that are unlikely to be reused again).
Keep in mind that updating entities in a local DaoSession is a bad idea if you use the entity in your global session as well. If you do this the cached entity in your global session won't be updated and you will get wrong results unless you clear the cache of the global session!
Thus the safest way is to either just use one DaoSession or new DaoSessions all the time and to not use a global and local sessions!!!
A custom application class is a good place, but any other class will also be ok.
This is how I do it:
class DBHelper:
private SQLiteDatabase _db = null;
private DaoSession _session = null;
private DaoMaster getMaster() {
if (_db == null) {
_db = getDatabase(DB_NAME, false);
}
return new DaoMaster(_db);
}
public DaoSession getSession(boolean newSession) {
if (newSession) {
return getMaster().newSession();
}
if (_session == null) {
_session = getMaster().newSession();
}
return _session;
}
private synchronized SQLiteDatabase getDatabase(String name, boolean readOnly) {
String s = "getDB(" + name + ",readonly=" + (readOnly ? "true" : "false") + ")";
try {
readOnly = false;
Log.i(TAG, s);
SQLiteOpenHelper helper = new MyOpenHelper(context, name, null);
if (readOnly) {
return helper.getReadableDatabase();
} else {
return helper.getWritableDatabase();
}
} catch (Exception ex) {
Log.e(TAG, s, ex);
return null;
} catch (Error err) {
Log.e(TAG, s, err);
return null;
}
}
private class MyOpenHelper extends DaoMaster.OpenHelper {
public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "Create DB-Schema (version "+Integer.toString(DaoMaster.SCHEMA_VERSION)+")");
super.onCreate(db);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "Update DB-Schema to version: "+Integer.toString(oldVersion)+"->"+Integer.toString(newVersion));
switch (oldVersion) {
case 1:
db.execSQL(SQL_UPGRADE_1To2);
case 2:
db.execSQL(SQL_UPGRADE_2To3);
break;
default:
break;
}
}
}
In application class:
private static MyApplication _INSTANCE = null;
public static MyApplication getInstance() {
return _INSTANCE;
}
#Override
public void onCreate() {
_INSTANCE = this;
// ...
}
private DBHelper _dbHelper = new DBHelper();
public static DaoSession getNewSession() {
return getInstance()._dbHelper.getSession(true);
}
public static DaoSession getSession() {
return getInstance()._dbHelper.getSession(false);
}
Of course you can also store the DaoMaster instead of the DB itself. This will reduce some small overhead.
I'm using a Singleton-like Application class and static methods to avoid casting the application (((MyApplication)getApplication())) every time I use some of the common methods (like accessing the DB).
I would recommend to create your database in your Application class. Then you can create a method to return the DaoSession to get access to the database in other Activities.

Sqlite database connection lost at real time use of application

I have my database class that is extending SQLiteOpenHelper which contains all the steps to create the database instance and executing queries.
DbAdapter.java
Creating database instance to be used throughtout my application------
public static DbAdapter getInstance(Context context) {
if (mInstance == null) {
DB_PATH = context.getFilesDir().getParent() + "/databases/";
mInstance = new IDDLDbAdapter(context);
try {
mInstance.createDataBase();
} catch (Exception e) {
e.printStackTrace();
}
}
return mInstance;
}
Cursor for rawQuery ----------
public synchronized Cursor rawQuery(String sql, String[] args) throws SQLException{
if(db == null || !db.isOpen())
this.open();
return db.rawQuery(sql, args);
}
However, there are times when the database adapter is no longer available although still unaware as it is happening at real time with customers using the application but not on application debug.
I have many classes where I am using this for connecting and executing queries just like that-
protected static DbAdapter dbAdapter;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try
{
dbAdapter = DbAdapter.getInstance(activity.getApplicationContext());
}
catch (ClassCastException e)
{
}
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
Button btn = (Button) view.findViewById(R.id.btnClick);
btn.setOnClickListener(new OnClickListener()
{
Cursor cur = dbAdapter.rawQuery("select * from blabla WHERE
Activities='"+ activity + "'", null);
cur.moveToFirst();
}
}
And then my work with the Cursor cur then goes on...
Is this code problematic ?
I have three doubts for the problem -
1) This line seems problematic --
dbAdapter = DbAdapter.getInstance(activity.getApplicationContext());
This is because it was advised in some posts to avoid using getApplicationContext() wherever possible.
2) During application idle time database connector class's object got finalized(having no reference to database).
3) I am getting the database instance onAttach(). Is it advisable ?
Can someone shed a light on the road of problems to move correctly ?

SQLiteOpenHelper getWritableDatabse() fails with no Exception

I have a very strange problem. It only shows from time to time, on several devices. Can't seem to reproduce it when I want, but had it so many times, that I think I know where I get it.
So I have a Loader which connects to sqlite through a singleton SQLiteOpenHelper:
try {
Log.i(TAG, "Get details offline / db helper: "+DatabaseHelper.getInstance(getContext()));
SQLiteDatabase db=DatabaseHelper.getInstance(this.getContext()).getWritableDatabase();
Log.i(TAG, "Get details offline / db: "+db);
//doing some work on the db...
} catch(SQLiteException e){
e.printStackTrace();
return null;
} catch(Exception e){
e.printStackTrace();
return null;
//trying everything to grab some exception or whatever
}
My SQLIteOpenHelper looks something like this:
public class DatabaseHelper extends SQLiteOpenHelper {
private static DatabaseHelper mInstance = null;
private static Context mCxt;
public static DatabaseHelper getInstance(Context cxt) {
//using app context as suggested by CommonsWare
Log.i("DBHELPER1", "cxt"+mCxt+" / instance: "+mInstance);
if (mInstance == null) {
mInstance = new DatabaseHelper(cxt.getApplicationContext());
}
Log.i("DBHELPER2", "cxt"+mCxt+" / instance: "+mInstance);
mCxt = cxt;
return mInstance;
}
//private constructor
private DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.mCxt = context;
}
#Override
public void onCreate(SQLiteDatabase db) {
//some tables created here
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//upgrade code here
}
}
It really works great in most cases. But from time to time I get a log similar to this:
06-10 23:49:59.621: I/DBHELPER1(26499): cxtcom.myapp#407152c8 / instance: com.myapp.helpers.DatabaseHelper#40827560
06-10 23:49:59.631: I/DBHELPER2(26499): cxtcom.myapp#407152c8 / instance: com.myapp.helpers.DatabaseHelper#40827560
06-10 23:49:59.631: I/DetailsLoader(26499): Get event details offline / db helper: com.myapp.helpers.DatabaseHelper#40827560
06-10 23:49:59.631: I/DBHELPER1(26499): cxtcom.myapp#407152c8 / instance: com.myapp.helpers.DatabaseHelper#40827560
06-10 23:49:59.651: I/DBHELPER2(26499): cxtcom.myapp#407152c8 / instance: com.myapp.helpers.DatabaseHelper#40827560
This line Log.i(TAG, "Get details offline / db: "+db); never gets called! No Exceptions, silence. Plus, the thread with the Loader is not running anymore.
So nothing past the line:
SQLiteDatabase db=DatabaseHelper.getInstance(this.getContext()).getWritableDatabase();
gets executed.
What can possibly go wrong on this line?

Categories

Resources