I have made Singelton object to make queries to SQLiteOpenHelper in which I have saved instance of getWriteableDatabase(). Throughout the application lifecycle, for all select and insert/update queries I am using that instance from multiple IntentServices. I have read Write-ahead Logging (WAL) which supports concurrent execution of queries. I am using the above instance with WAL disabled. Actually at a point the database does not return data, I was wondering if SQLite file can get corrupted because I am using getWritableabledatabse for reading/writing from multiple intent services.
Can a deadlock occur with this approach?
As per my findings, WAL should be enabled if you are accessing database from multiple threads.
EDIT
DatabaseAdapter.java
public class DatabaseAdapter {
private Context mContext;
private SQLiteDatabase mSqLiteDatabase;
private DatabaseHelper mDbHelper;
private static DatabaseAdapter adapter;
public static DatabaseAdapter getInstance() {
if(adapter == null) {
synchronized (DatabaseAdapter.class) {
if(adapter == null)
adapter = new DatabaseAdapter(MyApp.getInstance());
}
}
return adapter;
}
public DatabaseHelper getDatabaseHelper() {
return mDbHelper;
}
private DatabaseAdapter(Context c) {
mContext = c;
mDbHelper = new DatabaseHelper(mContext);
mSqLiteDatabase = mDbHelper.getWritableDatabase();
}
private long insert(String tableName, ContentValues contentValues) throws Exception {
this.open();
long id = mSqLiteDatabase.insert(tableName, null, contentValues);
this.close();
return id;
}
private int update(String tableName, ContentValues contentValues, int pk_id) throws Exception {
this.open();
String whereClause = mDbHelper.pk_id + " = " + pk_id;
int n = mSqLiteDatabase.update(tableName, contentValues, whereClause, null);
this.close();
return n;
}
private ArrayList<MyObject> selectChallans(String whereClause, String orderby) throws Exception {
try {
ArrayList<MyObject> arrayListObjects = new ArrayList<MyObject>();
Cursor queryCursor = mSqLiteDatabase.query(tableName, null, whereClause, null, null, null, orderby, null);
if (queryCursor == null) {
return null;
}
while (queryCursor.moveToNext()) {
MyObject myobject = getMyObject(queryCursor);
if(myobject != null)
arrayListObjects.add(myobject);
}
queryCursor.close();
return arrayListObjects;
} catch (Exception e) {
e.printStackTrace();
this.forceClose();
throw e;
}
}
}
I am using this Adapter singleton instance through the application for insert/update and select queries. I was concerned about mSqLiteDatabase instance. These functions are being called from multiple IntentServices.
AFAIK, the best practice is calling getWritableabledatabse only once with one SQLiteOpenHelper. After that, you can use the returned database for all thread without any issue. You have to make sure that you are using one database connection. You can check this Good Answer for more detail.
The SqliteOpenHelper object holds on to one database connection. It appears to offer you a read and write connection, but it really doesn't. Call the read-only, and you'll get the write database connection regardless.
So, one helper instance, one db connection. Even if you use it from multiple threads, one connection at a time. The SqliteDatabase object uses java locks to keep access serialized. So, if 100 threads have one db instance, calls to the actual on-disk database are serialized.
So, one helper, one db connection, which is serialized in java code. One thread, 1000 threads, if you use one helper instance shared between them, all of your db access code is serial. And life is good (ish).
For me, I usually create and open the SQLiteOpenHelper in Application class, then I can use it everywhere in any thread in my app.
Related
For example code below. Do we need to close cursor? Do we better use try/catch/finally instead of using if()?
public int getCount() {
final Cursor countCursor = contentResolver.query(
AnalyticContract.CONTENT_URI,
new String[] {"count(*) AS count"},
null,
null,
null);
if (countCursor == null) {
return 0;
}
countCursor.moveToFirst();
final int count = countCursor.getInt(0);
return count;
}
The try-with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try-with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.lang.AutoCloseable, which includes all objects which implement java.io.Closeable, can be used as a resource.
https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html
The answer I believe is primarily opinion-based. It depends I guess on coder's preference and the circumstances.
I have always preferred the if (cursor != null) or vice versa approach. Unless something truly spectacular has happened; which will be handled by throws Exception, I'd use if-else checks wherever I want the reader/reviewer to see which parts are really and truly exceptions and which are occurrences of different possible/valid scenarios.
This brings us to the current problem of Curosr and applying null checks.
AFAIK (since mostly a Cursor is related with a SQLiteDatabase) a ContentResolver.query() should never return a null Cursor if the query itself is valid unless in case of an invalid query which is a real exception and you should instead get an Exception.
So in my opinion the best approach would be using your example either
public int getCount() throws Exception {
Cursor countCursor;
try {
countCursor = contentResolver.query(
AnalyticContract.CONTENT_URI,
new String[] {"count(*) AS count"},
null,
null,
null);
countCursor.moveToFirst();
return countCursor.getInt(0);
}
finally {
cursor.close();
}
}
Or a variation where Exception is caught and handled within the method itself.
Now to answer your second question whether or not you should close() a Cursor: you should always close a Cursor. Whenever you don't have need for it. If you delve deeper into any of the Cursor.close() method-implementations. Since Curosr is an interface which in case of SQLite is implemented by SQLiteCursor you will notice that this method releases any and all allocations held by it.
I prefer to make a database helper class and through that database access becomes much much easier. Sample of a database helper Class -
public class DatabaseHelperClass extends SQLiteOpenHelper {
public DatabaseHelperClass(Context context)
{
super(context,"hakeem.db",null,1);
}
//Tables
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("Your SQL Query to create a table");
}
//Delete Tables
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table table_name if exists");
onCreate(db);
}
//Insertion of data into tables
long insertData(Various Parameters you like to pass for insertion)
{
SQLiteDatabase db=getWritableDatabase();
ContentValues values=new ContentValues();
values.put("col_name1",value);
values.put("col_name2",value);
values.put("col_name3",value);
values.put("col_name4",value);
return db.insert("signup_details",null,values);
}
//Delete record
public int deleteData(int id)
{
SQLiteDatabase sb=getWritableDatabase();
return sb.delete("hospital_details","id="+id,null);
}
//Update data in table
int updateData(Parameters you want to pass for update. Make sure you include a primary key)
{
ContentValues values=new ContentValues();
values.put("col_name1",value);
values.put("col_name2",value);
values.put("col_name3",value);
values.put("col_name4",value);
return getWritableDatabase().update("signup_details",values,"id="+id,null);
}
//Read data from tables
Cursor getSigninDetails() { return getWritableDatabase().rawQuery("select * from table_name",null); }
}
and to access results from the database-
private void getDataFromDatabase() {
Cursor cursor = db.getUserData();
if (cursor.moveToFirst()) {
do {
var_name1= cursor.getString(0);
var_name2= cursor.getString(1);
var_name3= cursor.getString(2);
var_name4= cursor.getString(3);
} while (cursor.moveToNext());
}
cursor.close();
}
the table ( i.e. vaccines) structure is :
id- auto increment primary key
dose1_date - string
dose2_date - string
The DatabaseAccessor class is as follows. The initDB() and setVaccineDates methods are called from another activity. But the database is not updated. The logged message is found in the logcat however. The DatabaseHelper class is not shown here.
public class DatabaseAccessor {
public static DataBaseHelper myDbHelper = null;
public static SQLiteDatabase rdb = null;
public static SQLiteDatabase wdb = null;
public static synchronized final void initDB(Context context) throws Exception {
if (myDbHelper == null) {
myDbHelper = new DataBaseHelper(context);
myDbHelper.openDataBase();
rdb = myDbHelper.getReadableDatabase();
wdb = myDbHelper.getWritableDatabase();
}
}
public static void setVaccineDates(String birthDate) throws SQLException{
try {
String[] selections = null;
String qry = null;
qry = "select * from vaccines order by id";
Cursor cursor = wdb.rawQuery(qry, selections);
Log.d("update qry===== ", qry);
while (cursor.moveToNext()) {
int rowID = Integer.parseInt(cursor.getString(0));
ContentValues values = new ContentValues();
values.put("dose1_date","66666");
values.put("dose2_date","7777");
wdb.update("vaccines", values, "id=?", new String[] {String.valueOf(rowID)});
//wdb.close();
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
}// end of method setVaccineDates
}
What to do ?
Edit : If I uncomment the wdb.close() line , I see in logcat
'06-09 04:21:05.387: W/System.err(4144): java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase: /data/data/com.cloudsoft.vaccine/databases/vaccines2.db
'
As a newbie in android it was just a mistake out of ignorance that this situation took place: after update operation I tried to find the changes in the database file (i.e. file with .db extension sitting inside assets folder in Eclipse) through sqlite browser . But what actually happens is the app running in the device (real one or emulator) has its own database which is created from the .db extension file inside assets folder and consequent database operations only affect the app's own database leaving no touch on the database inside the mentioned folder in Eclipse. And there is the way to watch the app's very own database in the running device in Eclipse's 'File Explorer' (in DDMS mode) with the help of Questoid SQlite Manager
Is this the appropriate way to do database access in an Android app? Should I be opening and closing database connections like this, or should I have one SQLiteDatabase object that I continually run queries against? Is my method for get specific column data from the cursor appropriate?
public List<Object> getObjects() {
SQLiteDatabase db = SQLiteDatabase.openDatabase(this.path, null, SQLiteDatabase.OPEN_READONLY);
List<Object> ret = new ArrayList<Object>();
Cursor cursor = db.rawQuery("select * from objects", null);
while(cursor.moveToNext())
{
Object obj = new Object();
obj.setId(cursor.getInt(cursor.getColumnIndex("ID")));
obj.setTitle(cursor.getString(cursor.getColumnIndex("Title")));
ret.add(obj);
}
db.close();
return ret;
}
The Singleton Pattern where you have on final static object to acces a database is the most common for databases
I have strange problem with android database and cursors. Time to time (very rarely) happens, that I got crash report from customers. It's hard to find out why it crashes, as I have ~ 150 000 active users and maybe 1 report per week or so, so it's really some minor bug. Here is exception:
STACK_TRACE=java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)
at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:599)
at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)
at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:144)
at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:133)
at sk.mildev84.agendareminder.a.c.a(SourceFile:169)
Before every cursor "iterating and exploring" I use this code to make sure everything is ok:
db = instance.getWritableDatabase();
cursor = db.rawQuery(selectQuery, null);
if (isCursorEmptyOrNotPrepared(cursor)) {
...
}
private synchronized boolean isCursorEmptyOrNotPrepared(Cursor cursor) {
if (cursor == null)
return true;
if (cursor.isClosed())
return true;
if (cursor.getCount() == 0) // HERE IT CRASHES
return true;
return false;
}
And it falls at line:
if (cursor.getCount() == 0)
Anyone know why? I think, I am checking all possible exceptions and conditions...Why is my app crashing here?
PS: All database methods are synchronized and I am correctly opening and closing database/cursors in all cases, I checked it many times.
Problem
If you try another operation after closing the database, it will give you that exception.Because db.close(); releases a reference to the object, closing the object if the last reference was released.
Solution
Keep a single SQLiteOpenHelper instance(Singleton) in a static context. Do lazy initialization, and synchronize that method. Such as
public class DatabaseHelper
{
private static DatabaseHelper instance;
public static synchronized DatabaseHelper getInstance(Context context)
{
if (instance == null)
instance = new DatabaseHelper(context);
return instance;
}
//Other stuff...
}
And you don't have to close it? When the app shuts down, it’ll let go of the file reference, if its even holding on to it.
i.e. You should not close the DB since it will be used again in the next call.
So Just remove
db.close();
For more info See at Single SQLite connection
The problem is clear that
SQLiteCursor cannot perform 'getCount' operation because the connection pool has been closed
To avoid IllegalStateException, we may keep the database open all the time if that is appropriate. In other situations we need to check the status before trying getCount.
My experience is as follows:
Defective Code:
SOLiteOpenHelper helper = new SOLiteOpenHelper(context);
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query(...);
if (cursor != null) {
cursor.getCount(); // HERE IT CRASHES
}
Perfect Code:
SOLiteOpenHelper helper = new SOLiteOpenHelper(context);
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor = db.query(...);
if (cursor != null && db.isOpen()) {
cursor.getCount(); // OK!
}
You just remove
Remove db.close()
I had this problem too. my SQLiteOpenHelper class was Singleton as well as closing the db after each CRUD operation.
After I make my methods(CRUD) synchronized in my SQLiteOpenHelper class, I didn't get error any more :)
Same problem occured to me, so after reading explanation I removed
db.close();
from
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
andpublic int delete(Uri uri, String selection, String[] selectionArgs)
method of ContentProvider No need of db.close() as ContentProvider itself take care of closing of database.
In an Android 'simple' database scenario, is there any benefit or reason to use database.close() and Not databaseHelper.close() ? Is there any benefit or reason to use databaseHelper.close() and Not database.close() ?
Is there a technical reason why both these close methods (shown below) exist?
Thanks,
James
MyDatabaseHelper databaseHelper = new MyDatabaseHelper(this);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues valuesToInsert = new ContentValues();
int id = 0;
valuesToInsert.put("_id", id);
valuesToInsert.put("name", "test");
database.insert("MyRecordsTable", null, valuesToInsert);
database.close();
OR
MyDatabaseHelper databaseHelper = new MyDatabaseHelper(this);
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues valuesToInsert = new ContentValues();
int id = 0;
valuesToInsert.put("_id", id);
valuesToInsert.put("name", "test");
database.insert("MyRecordsTable", null, valuesToInsert);
databaseHelper.close();
There isn't really a huge difference. This is the whole definition of close() within SQLiteOpenHelper:
/**
* Close any open database object.
*/
public synchronized void close() {
if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
if (mDatabase != null && mDatabase.isOpen()) {
mDatabase.close();
mDatabase = null;
}
}
The reason both exist, is that there may be instances where developers only use SQLiteOpenHelper for interfacing with their database and want the close() method as a convenience to directly access the DB, or vice versa if developers don't choose to use the OpenHelper at all.