I use the following code to add rows to my database :
public void insert(String kern, String woord) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KERN, kern);
values.put(WOORD, woord);
db.insertOrThrow(TABLE_NAME, null, values);
return;
Currently, I'm invoking this insert() 3.455 times, to add all words to the database, using : insert("Fruits", "Banana"); It takes forever.
How can I change this code to work faster? I'm thinking in the line of foreach, but don't know how to implement.. Thanks!
/Edit; The solution provided by #hovanessyan works and will do the job. AND.. note that if you have a lot of lines that have to be put in, you might be confronted with the method exceeds max byte limit error. In that case, review the other solution, that suggests packing the database in the actual .APK file.
You can wrap-up those inserts into transaction.
db.beginTransaction();
try {
// do all the inserts here
//method call here, that does 1 insert; For example
addOneEntry(kern,woord);
...
db.setTransactionSuccessful();
} catch (SQLException e) {
//catch exceptions
} finally {
db.endTransaction();
}
private void addOneEntry(String kern, String woord) {
//prepare ContentValues
//do Insert
}
You can use bulkInsert:
ContentValues[] cvArr = new ContentValues[rows.size()];
int i = 0;
for (MyObject row : rows) {
ContentValues values = new ContentValues();
values.put(KERN, myObject.getKern());
values.put(WOORD, myObject.getWoord);
cvArr[i++] = values;
}// end for
resolver.bulkInsert(Tasks.CONTENT_URI, cvArr);
Using the tips of both hovanessyan and Damian (remind me to rep+1 you as soon as I reach 15 ;), I came up with the following solution:
For relatively small databases (<1,5Mb)
I created the database using SQLite Database Browser, and put it in my Assets folder.
Then, the following code copies the database to the device, if it's not already there:
boolean initialiseDatabase = (new File(DB_DESTINATION)).exists();
public void copyDB() throws IOException{
final String DB_DESTINATION = "/data/data/happyworx.nl.Flitswoorden/databases/WoordData.db";
// Check if the database exists before copying
Log.d("Database exist", "" + initialiseDatabase);
Log.d("Base Context", "" + getBaseContext());
if (initialiseDatabase == false) {
// Open the .db file in your assets directory
InputStream is = getBaseContext().getAssets().open("WoordData.db");
// Copy the database into the destination
OutputStream os = new FileOutputStream(DB_DESTINATION);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0){
os.write(buffer, 0, length);
}
os.flush();
os.close();
is.close();
}}
In my app, a portion of the database is User-customizable.
I call the code above in onStart() with :
try {
copyDB();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
So, when the user presses "reset database to standard" (in preferences screen), I just set the Boolean initialiseDatabase to "false" and wait for the user to go back to the main activity. (thus calling onstart and copying the original database).
I tried to call the Activity.copyDB() from the preferences.java. It's neater, because it doesn't require the user to go back to the main activity to rebuild the database. However, I get an error about not being able to call static references to non-static methods. I don't understand that, but will look into it.
Related
I have two databases, one database is the primary. This primary DB is responsible for holding the current data which is up to date and my secondary DB is populated via a cron job, once the primary DB gets obsolete I want to replace it with the secondary DB via a file operation of just over writing the existing DB and refreshing my views. Is it possible to do this, is there a better way?
So far what I have done is:
public void writeToSD() throws IOException {
File f=new File("/mnt/sdcard/dump.db");
FileInputStream fis=null;
FileOutputStream fos=null;
try{
fis=new FileInputStream(f);
fos=new FileOutputStream("/data/data/com.one.two/databases/Bdr");
while(true){
int i=fis.read();
if(i!=-1){
fos.write(i);
}
else{
break;
}
}
fos.flush();
}
catch(Exception e){
e.printStackTrace();
}
finally{
try{
fos.close();
fis.close();
}
catch(IOException ioe){
System.out.println(ioe);
}
}
How about always using the same database files (let's say dbA, dbB) with two instances of SQLiteOpenHelper and using an utility class like this instead of using raw SQLiteOpenHelper:
class Db {
private SQLiteOpenHelper mPrimaryDb;
private SQLiteOpenHelper mSecondaryDb;
public Db(Context context) {
mPrimaryDb = new MyDbHelper(context, "db_a");
mSecondaryDb = new MyDbHelper(context, "db_b");
}
public SQLiteOpenHelper getPrimaryDb() {
return mPrimaryDb;
}
public SQLiteOpenHelper getSecondaryDb() {
return mSecondaryDb;
}
public void swapDb() {
SQLiteOpenHelper tmp = mPrimaryDb;
mPrimaryDb = mSecondaryDb;
mSecondaryDb = tmp;
// TODO: notify data users that data has changed, cleanup the old primary database, etc.
}
{
If you want to use file operations, renaming the data base files is faster. But during file operations all connections have to be closed before any action.
If insertion is too slow, I would not overwrite the database file. I would generate the new database with a temp name and the same table and view structure. After finishing writing to the temp file I would rename the file to the same name as the invariant part of the old database plus a version number or a timestamp . And in my application I would look periodically for a new version, if found I would close all connections to the old file and open the new database.
im having doubts on how to procede with database manipulation using Begin Transaction
I have 2 data source objects: ClientDataSource and MovementsDataSource
each of theres has its own SQLitedabase object which is set with the Open() method with sets its database private Object with a Writable Database.
Now, i have to send the Records from the Movements Table to a server and then ask for the updated Clients from the same server.
Inside my class i first Send the new Movements, then if sucessful, delete all clients from the SQLite, and finally retrieve the new Updated Clients from the server and insert them into the database.
each data source has a Get() method for obtainting their respective Database Object, BUT it is in fact the same database they are operating and I am not sure of how to use the BaginTransction, Endtransaction methods to ensure data consistency
this is the code from the Async Task
#Override
protected Boolean doInBackground(String... url) {
try {
clientDataSource.open();
movementDataSource.open();
} catch (SQLException e) {
//Treat SQL Exception
}
try {
if(sendMovements()) {
clientDataSource.deleteAllClients();
}
updateDatabase(url[0]);
} catch (JSONException e) {
//Treat Json Exception
} catch (IOException e) {
//Treat IOException
}
return true;
}
i have to give one example then how begintransaction works...
public static void Insert(ArrayList<Model_CategoryMaster> categoryMasters) {
SQLiteDatabase sqldb = EGLifeStyleApplication.sqLiteDatabase;
sqldb.beginTransaction();
for (Model_CategoryMaster model : categoryMasters) {
ContentValues values = new ContentValues();
values.put(CATEGORY_ID, model.Category_Id);
values.put(CATEGORYNAME, model.CategoryName);
values.put(DESCRIPTION, model.Description);
values.put(ISACTIVE, model.IsActive);
values.put(CREATEDON, model.CreatedOn);
values.put(CREATEDBY, model.CreatedBy);
values.put(UPDATEDON, model.UpdatedOn);
values.put(UPDATEDBY, model.UpdatedBy);
values.put(ISDELETED, model.IsDeleted);
values.put(DELETEDON, model.DeletedOn);
values.put(DELETEDBY, model.DeletedBy);
values.put(PK_CATEGORYMASTER, model.PK_CategoryMaster);
if (!CommonMethod.CheckIsDataAlreadyInDBorNot(Table_Name,
CATEGORY_ID, model.Category_Id)) {
sqldb.insert(Table_Name, null, values);
} else {
sqldb.update(Table_Name, values, "Category_Id=?",
new String[] { model.Category_Id });
}
}
sqldb.setTransactionSuccessful();
sqldb.endTransaction();
}// End insert method
begin transaction means data will get arraylist then db will open one time and sql.begintansaction() use to get faster database operation.because db is not open or close more times.
I need to include an existing SQLiteDatabase in my Android app and I also want the ability to download and install a new db. I did some research and my first working solution came from here. I didn't like that solution, for one thing, it assumes that the database will always be in a fixed path and other oddities.
So, rather than putting the existing database file in the assets, I exported the database to a SQL file, read it in, and then in the onCreate() method of my SQLiteOpenHelper, I call a new method updateDatabase with an open DataInputStream with the file data.
I ran into a few issues, which I think I solved, but I'm sure I didn't think of all the issues, which are:
When SQLiteOpenHelper's onCreate method is called, the database has been created, it is open, and inTransaction() is true. As a result, if the imported sql file includes BEGIN TRANSACTION, an exception is thrown and if the sql String contains statements creating 'android_metadata' yet another exception. So, I added a simple search using String.contains() looking for these keywords, and set a boolean doExecute to false to avoid executing them. So a question is, is there a better SQL class or method to filter this, or even a better regexp method?
Similar issue with having unexpected line breaks in the SQL file. I read the file with readLine() and to look for line breaks, I simply use String.trim() on the line, then check for endsWith(";"). This puts some constraints on my input file, like not having multiple statements on a single line. So, is there a better way to pre-process SQL from a file?
Here's the code I use to create my db after I've gotten a DataInputStream from the assets resource or from a download:
public boolean updateDatabase(DataInputStream inStream, SQLiteDatabase db, boolean doClear) throws Error {
String sqlStatement = null;
boolean result = true;
boolean inOnCreate = true;
boolean wasInTransaction;
if(doClear) dropDatabase();
// if called from onCreate() db is open and inTransaction, else getWritableDatabase()
if(db == null) {
inOnCreate = false;
db = this.getWritableDatabase();
}
wasInTransaction = db.inTransaction(); // see NB below
boolean doExecute;
try {
while ((sqlStatement = inStream.readLine()) != null) {
// trim, so we can look for ';'
sqlStatement.trim();
if(!sqlStatement.endsWith(";")) {
continue; // line breaks in file, get whole statement
}
// NB - my file (exported from SQLite Database Browser starts with "BEGIN TRANSACTION;".
// executing this throws SQLiteException: cannot start a transaction within a transaction
// According to SQLiteDatabase doc for beginTransaction(), "Transactions can be nested"
// so this is a problem
// but... possibly it is an "exclusive transaction" ?
doExecute = true;
if(wasInTransaction) {
// don't execute BEGIN TRANSACTION; or COMMIT;
if((sqlStatement.contains("BEGIN" ) || sqlStatement.contains("begin" )) &&
(sqlStatement. contains("TRANSACTION") || sqlStatement.contains("transaction" ))) {
doExecute = false;
}
if(sqlStatement.contains("COMMIT") || sqlStatement.contains("commit")) {
doExecute = false;
}
} // inTransaction
// this statement could be in older databases, but this scheme doesn't need, can't have it
if(sqlStatement.contains("android_metadata")) {
doExecute = false;
}
if(doExecute) {
try {
db.execSQL(sqlStatement);
} catch (SQLException e) {
throw(new Error("Error executing SQL " + sqlStatement));
} // try/catch
} // doExecute
} // while()
} catch (IOException e) {
result = false; // which won't matter if we throw
throw(new Error("Error reading " + DB_SQL));
}
if(!inOnCreate) {
db.close();
}
return result;
}
Wouldn't want to have you cop out early on such ambitious and elegant implementations, but if you have your database already made and checked with a database browser and all, have you considered SQLite Asset Helper? If your main issue was being forced to use the asset folder, this method lib let's you use a file from any specified directory. Moreover, it allows for handling the raw .db file. Worth checking out.
My question is that I want to restore my application's sqlite database after I overwrite the same app on my device. I don't like to add settings again and again on start up of my app.
So is it possible in android to save the database somewhere from which I can again restore it ?
I have searched for more than hours on Google and SO but couldnt ind any solution.
EDIT: Its not a fixed database. So I can't store it in Assets Folder. It is editable by user but by default it should carry the last edited values(values before the app overwrite).
This method I find very helpful:
public static void movedb(File srcdb, File destdb)
{
try
{
if (Environment.getExternalStorageDirectory().canWrite())
{
if (srcdb.exists())
{
FileChannel src = new FileInputStream(srcdb).getChannel();
FileChannel dst = new FileOutputStream(destdb).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
}
else
{
//ERROR: "Database file references are incorrect"
}
}
else
{
//ERROR: "Cannot write to file"
}
}
catch (Exception e)
{
//ERROR: e.getMessage()
}
}
Then I just call
movedb(this, new File(<context>.getDatabasePath("...your DB name...")), new File("... your location ..."));
To back up, and then to restore:
movedb(this, new File("... your location ..."), new File(<context>.getDatabasePath("...your DB name...")));
I'm using ORMLite and, apart from storing the database in the external public directory, after I restore the file to the database directory, I have to re-instantiate the DatabaseHelper singleton and create a new one.
Here is my version, omitting every try/catch block for the sake of simplicitiy:
public boolean restoreBackup(Context context){
String databasePath = "data/data/my.package.name/databases/myDatabase.sqlite";
String backUpPath = context.getDatabaseDir("myDatabase.sqlite");
// Copies back-up to database directory
new File(databasePath).delete();
FileInputStream streemToBackUp = new FileInputStream(new File(backUpPath));
OutputStream streamToDatabaseFile = new FileOutputStream(databasePath);
byte[] buffer = new byte[1024];
int length;
while ((length = streamToBackUp.read(buffer)) > 0) {
streamToDatabaseFile.write(buffer, 0, length);
}
streamToDatabaseFile.flush();
streamToDatabaseFile.close();
streamToBackUp.close();
// Re-instantiate DatabasHelper singleton
DatabaseHelper.closeHelper();
}
The body of closeHelper() is as follows:
public static void closeHelper() {
helper.close();
}
#Override
public void close() {
super.close();
myDao = null; // Set to null every day you have
helper = null; // Set to null the singleton instance of the helper
}
This will work as long as you don't use OpenHelperManager class to instantiate the helper, and always use getHelper() whenever you need the database instead of storing the instance returned.
I have a problem with a database file not being read
I have added the database file in assets called mydb but when i run my code it says its not being located. It is calling this toast Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show(); This is being called because no records are being returned. Also I know it is finding the file as there is no FileNotFoundException exception. This is an example form Android Application Development book.
public class DatabaseActivity extends Activity {
/** Called when the activity is first created. */
TextView quest, response1, response2;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView quest = (TextView) findViewById(R.id.quest);
try {
String destPath = "/data/data/" + getPackageName() + "/databases/MyDB";
File f = new File(destPath);
if (!f.exists()) {
CopyDB( getBaseContext().getAssets().open("mydb"),
new FileOutputStream(destPath));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
DBAdapter db = new DBAdapter(this);
//---get a contact---
db.open();
Cursor c = db.getContact(2);
if (c.moveToFirst())
DisplayContact(c);
else
Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show();
db.close();
}
public void CopyDB(InputStream inputStream, OutputStream outputStream)
throws IOException {
//---copy 1K bytes at a time---
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
}
public void DisplayContact(Cursor c)
{
quest.setText(String.valueOf(c.getString(1)));
//quest.setText(String.valueOf("this is a text string"));
}
}
Is there a better way to upload data.
A couple of things come to mind here...
because of the !f.exists() check, then once the database exists (and maybe empty) then it will never copy it again. So maybe for now, copy it all the time, until you work out kinks and then add in the !f.exists()
I've had mixed results with e.printStackTrace(), maybe change to Log.e(TAG, "message", e) and see if you start seeing errors showing up in LogCat
As for a better way... I've done this a couple different ways...
1. Is to create a file (json, cvs, etc) and then process and load it, if the database is empty
2. Similar to the first, except that I create a java serialized object array and load it to the database, if the database is empty.
Also I don't know what DBAdapter looks like, and since it wraps the database the issue may be there.