I created a SQlite DB. It is no problem to write or read from my DB. In my DBHandler.class I added some methods to read one column for example. Every method has dbHandler.close() line. If I want to call a method from the DBHandler.class, so I have to make something like:
... DBHandler db = new DBHandler(this); ... list = db.getAllBlock()
Shoud I put the line
db.close()
after
list...
?????
That is my problem to understand.... Thank you!
It's inefficient to constantly have to reconnect to the database everytime you want to do something, so remove the close() statements from each line and create a single instance of the DB Handler class:
It's much better to have a single DBHandler
DBHandler db;
public Activity/Class Constructor()
{
DBHandler db = new DBHandler(this);
}
private void DoSomethingWithDatabase()
{
db.doSomethingHere();
}
private void DoSomethingWithDatabaseAgain()
{
db.doSomethingThere();
}
public void Activity/ClassClose()
{
db.CloseDatabase();
}
instance inside which ever Activity/Class is using it, then have a close method in DBHandler, which closes the connection to the database after the activity/class is finished using it.
public class DBHandler
{
private static SQLiteDatabase m_DB;
public DBHandler open() throws android.database.SQLException
{
m_DB = m_DBHelper.getWritableDatabase();
}
public void doSomethingHere()
{
m_DB.getAllBlock()
}
public void doSomethingThere()
{
m_DB.getAllSomethingElse()
}
public void CloseDatabase()
{
m_DB.close();
}
}
In fact, it's even better to have the DBHandler as a Singleton then you can have app wide database access without the inefficient overheads of re-establishing connections every time you want it.
Related
I am trying to write test cases for my app database. This is what I do in the setup method:
#Before
public void testCaseSetUp() {
RenamingDelegatingContext context = new RenamingDelegatingContext(getTargetContext(), "test_");
dbController = new DBController(context);
dbController.open();
}
DBController.java
public DBController(Context c) {
context = c;
}
public DBController open() throws SQLException {
dbHelper = DBHelper.getInstance(context);
database = dbHelper.getWritableDatabase();
return this;
}
RenamingDelegatingContext does not create a new test database instead uses the existing db file. This is causing my test cases to fail as I already have data.
Are you sure that you recreate your SQLiteOpenHelper somewhere inside your DBHelper class? From the looks of it the latter is a singleton, so I can assume you're not recreating it every time. You should do so in your #Before method, because the idea behind using a RenamingDelegatingContext here is that it deletes old databases before opening a new one.
Also it's a good practice to close the SQLiteOpenHelper class after your tests. Something like:
#After
public void tearDown() {
dbHelper.close();
}
If this doesn't work, please post your DBHelper class to verify it's ok.
Here is my problem. My app starts several threads, each for a particular object to be updated. The update of the object happens with a query to a single database. There is a single database and a single OpenHelper. The behavior of my app suggests me that the calls to the database are non simultaneous as well as I would like. How can I access the same database from different threads simultaneously? If the data for each object are in different tables is more efficient to split the database in several databases, one for each object?
public class SomethingToBeUpdated implements Runnable {
private SQLiteDatabase db;
#Override
public void run() {
db.rawQuery( ... bla bla
}
}
public class MainActivity extends Activity {
private SomethingToBeUpdated[] list = bla bla...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
for( SomethingToBeUpdated x : list ) {
new Thread(x).start();
}
}
}
For the sake of accessing the database in various threads you need to have a Database manager which keeps an object of your database class and pass it to any thread that needs it. In android you cannot access database simultaneously in several threads with different objects. It may just block your UI (the problem i was facing a few days ago).
So to overcome this problem you can use the the database manager i used which is defined as follows:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
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() {
if(mOpenCounter.incrementAndGet() == 1) {
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
mDatabase.close();
}
}
}
Then you initialize it once like this:
DatabaseManager.initializeInstance(new ChatSQL(c));
And then you can get the database object wherever you want with this syntax:
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase(); //in your methods which are querying the database
With this method your database is now thread safe. Hope this helps.
If the data for each object are in different tables is more efficient to split the database in several databases, one for each object?
No it is not efficient. It has a lot of overheads to define, access, make object and query different databases. And what if you want to join tables? you just cannot.
Exception:
CREATE TABLE android_metadata failed
Failed to setLocale() when constructing, closing the database
android.database.sqlite.SQLiteException: database is locked
My app works fine and has no db issues, except when onUpgrade() is called.
When onUpgrade is automatically called, it tries to use the CarManager class below to do data manipulation required for the upgrade. This fails because the db is locked.
Because this seems like it should be a normal thing to do, it seems that I must not be structuring the following code correctly (two classes follow, a helper and a table manager):
public class DbHelper extends SQLiteOpenHelper {
private Context context;
//Required constructor
public DbAdapter(Context context)
{
super(context, "my_db_name", null, NEWER_DB_VERSION);
this.context = context;
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
overrideDB = db;
CarManager.migrateDataForOnUpgrade(context);
}
}
public class CarManager {
DbHelper dbHelper;
public CarManager(Context context)
{
dbHelper = new DbHelper(context);
}
public void addCar(String make, String model)
{
ContentValues contentValues = new ContentValues();
contentValues.put("make", make);
contentValues.put("model", model);
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.insert("car", null, contentValues);
db.close();
}
public static void migrateDataForOnUpgrade()
{
//Code here that migrates data when onUpgrade() is called
//Db lock happens here
}
}
Any ideas?
Do people set up table manager (ex: dao) differently than this?
edit: I talked to the google team # android developer hours, and they said onUpgrade3 was never meant to do anything like structural changes (alters). So yes, it seems like there are some hacks that must be used in many instances right now.
I use the following model by extending the Application class. I maintain a single static instance of my db helper which all other app components use...
public class MyApp extends Application {
protected static MyAppHelper appHelper = null;
protected static MyDbHelper dbHelper = null;
#Override
protected void onCreate() {
super.onCreate();
...
appHelper = new MyAppHelper(this);
dbHelper = MyAppHelper.createDbHelper();
dbHelper.getReadableDatabase(); // Trigger creation or upgrading of the database
...
}
}
From then on any class which needs to use the db helper simply does the following...
if (MyApp.dbHelper == null)
MyApp.appHelper.createDbHelper(...);
// Code here to use MyApp.dbHelper
I'm trying to get a better understanding of the SQLiteOpenHelper class and how and when onCreate and onUpgrade are called.
My problem is that each time I quit and start my application (technically it's actually each time I create a new instance of MyDB), onCreate is called, and all of the data from the previous usage is effectively wiped... WTF???
I got around this problem initially by creating a singleton MyDBFactory where I created a single instance of MyDB. This allowed the data to be persistent while the application is running at least.
What I would like is for my database schema and data to be persistent!
I basically have:
public class MyDB extends SQLiteOpenHelper{
private static int VERSION = 1;
...
public ContactControlDB(Context context) {
super(context, null, null, VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
try {
db.execSQL(DATABASE_CREATE);
db.execSQL(INSERT_DATA);
} catch (SQLException ex) {
ex.printStackTrace();
}
}
and:
public class MyDBFactory{
private static MyDB db;
public static MyDB getInstance(Context context) {
if(db == null) {
db = new MyDB (context);
}
return db;
}
}
What I'd like to know is why onCreate is called every time I have 'new MyDB(context)', and where my database goes each time my app exits.
If you have some appropriate links, or some knowledge that would clue me up a bit, I'd greatly appreciate it!
the line:
super(context, null, null, VERSION);
the second parameter is null specifying that the database should be created in memory, you should give it a name.
Android reference
I wanna make SQLiteOpenHelper with some additional methods (like getStreetsCursor) which returns data from my db. So i wrote something like this:
public class DBHelper extends SQLiteOpenHelper {
public static final String DB_NAME="some.db";
public static final String T1_NAME="streets";
public static final String T1_FNAME1="name";
public static final String T2_NAME="addresses";
public static final String T2_FNAME1="name";
public static final String T2_FNAME2="address";
private Context appContext;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
appContext=context;
}
public Cursor getStreetsCursor(String chars) {
SQLiteDatabase dbReadable=this.getReadableDatabase();
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
return curStreets;
}
There are several methods like getStreetsCursor (getAddresses, getAddress4 etc) defined in DBHelper.
I guess if it is a DB Helper it definitely should have such methods i mean the DBHelper is a logical placeholder for them.
What i do in the activity is create a new DBHelper instance and store it in private field (called mDBHelper) of activity. Additionally in onDestroy method of activity i have mDBHelper.close().
private DBHelper mDBHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDBHelper = new DBHelper(this);
...
#Override
protected void onDestroy() {
if (mDBHelper!=null){
mDBHelper.close();
Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
}
super.onDestroy();
}
Those activities uses the mDBHelper only in one way - by calling it's custom methods like mDBHelper.getStreetsCursor().
Eventually I found an exception message in logcat about my app got leaks in such activities which uses DBHelper. It says something like "database was never closed". So i decided to add a call to close() method in each of my custom methods just before return. So it looks like:
public Cursor getStreetsCursor(String chars) {
SQLiteDatabase dbReadable=this.getReadableDatabase();
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
dbReadable.close();
return curStreets;
}
Now i got no leaks but got the next problem - only first call to mDBHelper.getStreetsCursor() really executes. All of the next calls returns null. Thats due to dbReadable.close(); line. If i remove it everythig works fine but i got leaks again.
So i cant figure out whats going wrong. In every custom method i got SQLiteDatabase dbReadable=this.getReadableDatabase(); line which should return a readable instance but after executing the close() method it doesn't.
I guess its about my custom methods because they calls .getReadableDatabase() inside an instance of DBHelper. If i place those methods directly in activity everything works fine - no leak exceptions and every time methods returns a proper data. But i want to place those methods in my DBHelper class.
So the main question is - what's wrong and how to do that properly?
You shouldn't have a SQLiteDatabase dbReadable=this.getReadableDatabase();
in every method getSomethingCursor as requesting a database object is expensive ( I think I read it in SO).
So you can create the object from your constructor
SQLiteDatabase dbReadable;
public DBHelper(Context context) {
super(context, DB_NAME, null, 1);
appContext=context;
dbReadable=this.getReadableDatabase()
}
public Cursor getStreetsCursor(String chars) {
Cursor curStreets = dbReadable.query(DBHelper.T1_NAME,
new String[] {"_id",DBHelper.T1_FNAME1},
DBHelper.T1_FNAME1+" LIKE(\""+chars.toUpperCase()+"%\")",
null, null, null, DBHelper.T1_FNAME1);
return curStreets;
}
Create a method to close the database handle:
public closeDb() {
if (dbReadable != null) { dbReadable.close();}
}
And in you activity:
#Override
protected void onDestroy() {
if (mDBHelper!=null){
mDBHelper.closeDb();
Log.i(APP_TAG,"mDBHelper.close() in "+this.getClass());
}
super.onDestroy();
}
And use startManagingCursor (if SDK < HoneyComb) to let your activity manage your cursor (closing it on onDestroy for example
Make SQLiteDatabase dbReadable a class member, and implement
public void close()
{
dbReadable.close();
}
Then, when you call mDBHelper.close(); in your activity's onDestroy() function, it should be ok.
Make a new function
public void DBclose()
{
curstreets.close();
dbReadable.close();
}
Then you can call this in your activity in onDestroy() fucntion.
Years after... I just want to state that now I'll better use Realm.io DB engine and I do it now in my projects. As for my old question I also want to notice that using getReadableDatabase() is a bad idea since across devices fragmentation I met some curios sqlite bug - readable instance will be closed automatically right after a query execution which is unexpected behavior. So I recommend always use getWritableDatabase() method even for reading purpose. And finally I end up with Dmytro Danylyk solution - give it a try, Dmytro is Google Developer Expert:
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
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() {
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 use it as follows.
SQLiteDatabase database = DatabaseManager.getInstance().openDatabase();
database.insert(...);
// database.close(); Don't close it directly!
DatabaseManager.getInstance().closeDatabase(); // correct way