Android database transactions - android

I am new to Android programming and am trying to understand the best practices.
I want to do multiple inserts into two different database tables, but as one transaction (as the tables shared a foreign key). I want my function to return a result so that I can display a Toast or something to say that an error occurred, otherwise I want to return the row ID of the first insert.
I believe one way of doing this is sort-of as follows (Disclaimer: psuedo-ish code, probably won't compile!):
Long result = -1;
myDatabase.beginTransaction();
try {
// Insert into first table
ContentValue someValues = new ContentValues();
someValues.put("dbfield1", 1);
result = myDatabase.insert(DATABASE_TABLE_1, null, someValues);
if (-1 != result ) {
// Insert into second table
someValues.clear();
someValues.put("dbfield2", 2);
if( myDatabase.insert(DATABASE_TABLE_2, null, someValues) < 0 ) {
result = -1;
}
}
mDatabase.setTransactionSuccessful();
} catch(Exception e) {
// An error occurred
result = -1;
} finally {
mDatabase.endTransaction();
}
Is there a simpler/better way of doing this?

You can override bulkInsert inside your ContentProvider
your Code looks fine. The Method should return the inserted Rows Value but you can customize that so you return only the first ID.
public int bulkInsert(Uri uri, ContentValues[] values) {
Log.e("BULK", "Bulk insert started for URI" + uri.toString());
bulkSqlDB = database.getWritableDatabase();
int numInserted;
bulkSqlDB.beginTransaction();
try {
for (ContentValues cv : values) {
insert(uri, cv);
}
bulkSqlDB.setTransactionSuccessful();
numInserted = values.length;
} finally {
getContext().getContentResolver().notifyChange(uri, null);
bulkSqlDB.endTransaction();
}
return numInserted;
}

Related

How to check data in SQLite if already exist update or else insert in android

I want to check the data in SQLite if already exist can update or else insert.I am checking code like this what i mentioned below.
Code:
public long addmenus(String navigationdrawer,String optionname)
{
SQLiteDatabase menus=this.getWritableDatabase();
try {
ContentValues values=new ContentValues();
values.put(HEADER_NAME,navigationdrawer);
values.put(CHILD_NAME,optionname);
// menus.insert(TABLE_NAME,null,values);
// String owner=optionname;
Cursor cursor = menus.rawQuery("select * from TABLE_NAME where CHILD_NAME ='"+ optionname +"'", null);
if(cursor.getCount()<1)
{
//execute insert query here
long rows = menus.insert(TABLE_NAME, null, values);
return rows;
// return rows inserted.
}
else
{
//Perform the update query
String strFilter = "CHILD_NAME" + optionname;
long updaterow=menus.update(TABLE_NAME,values,strFilter,null);
return updaterow;
// return rows updated.
}
// menus.close();
} catch (Exception e) {
return -1;
}
finally {
if (menus != null)
menus.close();
}
}
My activity:
I converted whole json data into string object then insert into SQLite.
String productpage=jsonObject.toString();
db.addmenus(productpage,"Navigationmenus");
But It doesn't work.It couldn't insert into sqlite.
Anyone solve this problem Glad to appreciate.
Thanks in advance
You can user insertWithOnConflict() like this
db.insertWithOnConflict(TABLE, null, yourContentValues, SQLiteDatabase.CONFLICT_REPLACE);
You can use refer this Link. That link explains how to find the email address available in a table or not, you can change the column name, table and pass the values according. In your scenario you want to check the whether the name exists already or not, so you must pass which name you want to find. If the name is there then this method will return true or false. You can validate whether you had to insert or update according the response.i.e., false means you had to insert, otherwise if it is true means then you had to update.
you should use replace into
REPLACE INTO table(...) VALUES(...);
Question is not much clear but, i think you want to check either data/record is inserted in SQLite or not. you will need to define some extra variable long rowInserted insert() method returns the row ID of the newly inserted row, or -1 when an error occurred.
menus.insert(TABLE_NAME, null, values);
long rowInserted = db.insert(AddNewPhysicalPerson, null, newValues);
if(rowInserted != -1)
Toast.makeText(myContext, "New row added :" + rowInserted, Toast.LENGTH_SHORT).show();
else
Toast.makeText(myContext, "Something wrong", Toast.LENGTH_SHORT).show();
Updated
check either data is in table or column? for this you use this code
public boolean Exists(String id){
Cursor res = getAllData();
int count=0;
while (res.moveToNext()){
String email =res.getString(3);
if(email.equals(id)){
count++;
}
}
if(count==0){
return false;
} else{
return true;
}
}
Second you asking about json first store all data in any List run time and get string from it then you are able to store in SQlite
try {
items = jsonObject.getJSONArray("myjsonattribute");
List<MyAnySetterGetter> mList = new ArrayList<MyAnySetterGetter>();
for (int i = 0; i < items.length(); i++) {
JSONObject c = items.getJSONObject(i);
String mfilename = c.getString("myjsonattribute2");
mList.add(mfilename);
}
} catch (JSONException e) {
//e.printStackTrace();
}
then use above list to insert data from list to SQLite
like
String str1 = mList.get(position).getMYITEM1();
String str2 = mList.get(position).getMYITEM2();
insert str1 and str2 in SQLite hope you will get idea.
you should
set key for the table, then
insert(if the key existed it will not insert anymore), then
update all row.

Using WHERE Clause SQLite database in Android?

This is my code:
public int getIdMotChuDe(String tenChuDe) {
int IDChuDe = 0;
try
{
Cursor c = null;
c = database.rawQuery(
"SELECT ChuDeID FROM DanhSachChuDe WHERE TenChuDe = ?"
, new String[] {tenChuDe});
c.moveToFirst();
IDChuDe = c.getInt(c.getColumnIndex("ChuDeID"));
c.close();
}
catch(Exception e)
{
e.printStackTrace();
}
return IDChuDe;
}
I'm trying to get ChuDeID from DanhSachChuDe table with condition in WHERE clause. But i don't know why this function always return 0.
Help me please. Thanks! Sorry because my english.
This could be because an Exception is being thrown. The code you are using is not correctly checking the state of your Cursor - it attempts to moveToFirst() before any checking too see if the object is not null.
Your code also assumes a result is always returned. This is bad practise, and should be avoided. A much safer and more common solution is the following:
if (cursor != null) {
// If the cursor has results, move the cursor to first row
if (cursor.moveToFirst()) {
do {
// YOUR METHODS HERE
// then move to next row
} while (cursor.moveToNext());
}
}

catch error when user is trying to insert duplicated value in database - greenDAO

im using greenDAO to generate my DAO classes, everything works fine till i try to insert duplicated values for primary key.
It fails when i try to insert duplicated primery key (as expected), but i would like to send the exception to the user and not o explode with the app.
For now the only solution i have, and i think its not the best, is to do a query before insert, and check if the result count is greater than 0 or not.
There is a better way to do it?
the method .insert returns a Long value, but when it fails to insert it doesn't return anything, just explodes with the app.
Best would be to keep your primary key incremental away from what the user supplies as a key. However that is my personal choice, but aids when I have to check the last inserted ID, Ill do something like:
public int getPrimaryId() {
final String MY_QUERY = "SELECT MAX(_id) FROM " + DATABASE_TABLE0;
Cursor cur = mDb.rawQuery(MY_QUERY, null);
cur.moveToFirst();
int ID = cur.getInt(0);
cur.close();
return ID;
}
In your case you can preload an array list with primaryID's once user supplies a new key just check it with the elements in the arraylist. Faster!
Update: To Check if ID exists.
SELECT EXISTS(SELECT 1 FROM myTbl WHERE ID="anyID" LIMIT 1);
The LIMIT will ensure that once found there is no need to go further. This would be super fast. Plus Exists will always return a result.
Also suggested would be the use of proper indexes
Surround your insert-statement with a try-catch-block
try {
dao.insert (entity);
} catch (Exception ex) {
// Do your handling here
} catch (Error e) {
// and here
}
I thought it would be another way to control it, but i found my way without doing this SELECT.
Here are two solutions:
With SELECT
With SELECT (proposed by other user on this post - check it for more details)
public int getPrimaryId() {
final String MY_QUERY = "SELECT MAX(_id) FROM " + DATABASE_TABLE0;
Cursor cur = mDb.rawQuery(MY_QUERY, null);
cur.moveToFirst();
int ID = cur.getInt(0);
cur.close();
return ID;
}
Override method
Override .insert() method, inside myObjectDAO.java
#Override
public long insert(Stud_Course entity) {
long ret = 0;
try {
ret = super.insert(entity);
} catch (Exception e) {
ret = 0;
Log.e("LOG", "fail to insert!!!");
}
return ret;
}

Bulk inserting using an array of ContentValues

im trying to do a batch insert of about 700 floats. The method i'm using is below and as well as the content provider's bulkInsert. The issue is that when i put all the floating point values into the ContentValues nothing happens. What's a better way to insert those floating point values into the ContentValues object?
private void saveToDatabase( float[] tempValues )
{
ContentValues values = new ContentValues();
// WM: TODO: add patient id and sensor type
for (float tempVal : tempValues){
values.put( DataTable.COLUMN_DATA, tempVal );
}
ContentValues[] cvArray = new ContentValues[1];
cvArray[0] = values;
ContentResolver resolver = getContentResolver();
resolver.bulkInsert( HealthDevContentProvider.CONTENT_URI_DATA, cvArray);
public int bulkInsert(Uri uri, ContentValues[] values){
int numInserted = 0;
String table = null;
int uriType = sURIMatcher.match(uri);
switch (uriType) {
case RAWINPUT_TABLE:
table = RAWINPUT_TABLE_PATH;
break;
}
db.beginTransaction();
try {
for (ContentValues cv : values) {
long newID = db.insertOrThrow(table, null, cv);
if (newID <= 0) {
throw new SQLException("Failed to insert row into " + uri);
}
}
db.setTransactionSuccessful();
getContext().getContentResolver().notifyChange(uri, null);
numInserted = values.length;
} finally {
db.endTransaction();
}
return numInserted;
}
If you want each float to have it's own record in your database, you need an instance of ContentValues for each new record. Right now you have one instance of ContentValues and you are writing the same key to it (meaning you are writing over the value) 700 times.
private void saveToDatabase( float[] tempValues ) {
final int count = tempValues.legnth;
ContentValues[] cvArray = new ContentValues[count];
for (int i = 0; i < count; i++) {
float tempVal = tempValues[i];
ContentValues values = new ContentValues();
values.put( DataTable.COLUMN_DATA, tempVal );
cvArray[i] = values;
}
/* all the rest */
}
I know that this will be rude, but just throw away this code. Providers have primary methods to deal with most SQLite operations and you tried to blend three of them (insert(), bulkInsert(), and applyBatch()) into some kind of Frankenstein. Here are the main mistakes:
1) This line values.put(DataTable.COLUMN_DATA, tempVal) is not inserting new entries at each iteration; it is overriding them. After all iterations, values contains only the 700th float value of your array.
2) As #Karakuri remembered, there is only one ContentValues instance inside cvArray. bulkInsert() doc states about its second parameter:
An array of sets of column_name/value pairs to add to the database. This must not be null.
So cvArray must contain a ContentValues instance (a set) for every entry you want to insert into the database.
3) Not exactly an error, but something you should watch out. There are no guarantees that mTables will exist, and trying to make operations without specifying a table will throw a SQLException.
4) These three lines are basically useless:
if (newId <= 0) {
throw new SQLException("Failed to insert row into " + uri);
}
insertOrThrow() already throws an exception if some error happens during the insert operation. If you want to check manually for an error, try insert() or insertWithOnConflict() (or add a catch to your try block and deal with the exception there).
5) And finally, there is the problem about numInserted #petey pointed (and there's no need to repeat).
One last advice: forget that bulkInsert() exists. I know that this will require more lines of code, but using applyBatch() you can achieve better results (and more easily, since you do not have to implement it). Wolfram Rittmeyer wrote a series of excellent articles about transactions, check if you have any doubt.
Last but not least (yes, I'm in a good mood today), this is how I would do a basic implementation of your code:
#Override
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db // TODO: retrieve writable database
final int match = matcher.match(uri);
switch(match) {
case RAWINPUT_TABLE:
long id = db.insert(RAWINPUT_TABLE, null, values); // TODO: add catch block to deal.
getContext().getContentResolver().notifyChange(uri, null, false);
return ContentUris.withAppendedId(uri, id);
default:
throw new UnsupportedOperationException("Unknown uri: " + uri);
}
}
private void saveToDatabase( float[] tempValues ) {
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
for (float tempVal : tempValues){
operations.add(ContentProviderOperation
.newInsert(HealthDevContentProvider.CONTENT_URI_DATA)
.withValue(DataTable.COLUMN_DATA, tempVal).build();
.withValue() // TODO: add patient id
.withValue() // TODO: add sensor type);
}
// WARNING!! Provider operations (except query if you are using loaders) happen by default in the main thread!!
getContentResolver().applyBatch(operations);
}
I use batch inserts, not sure what the difference between bulk and batch is but all I do is this
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
for(int j=0;j<locationAry.length;j++){
ContentValues values2 = new ContentValues();
values2.put(MapPoints.ELEMENT_ECM2ID, ecm2id);
values2.put(MapPoints.ELEMENT_ID, newElementId);
values2.put(MapPoints.LATITUDE, locationAry[j+1]);
values2.put(MapPoints.LONGITUDE, locationAry[j]);
values2.put(MapPoints.LAYER_ID, layerID);
operations2.add(ContentProviderOperation.newInsert(MapPoints.CONTENT_URI).withValues(values2).build());
}
getContentResolver().applyBatch(MapElements.AUTHORITY, operations);
did you override the bulkInsert method in your ContentProvider?
If one insert fails, your whole transaction fails. Without seeing your table create statement for unique keys, try a replace after your insert fails.. Also your numInserted will always be the same as values.length no matter what insert/replace fails. this doesnt seem correct either.
...
db.beginTransaction();
int numInserted = 0;
try {
for (ContentValues cv : values) {
long newID;
try {
newID = database.insertOrThrow(table, null, cv);
} catch (SQLException ignore) {
newID = database.replace(table, null, cv);
}
if (newID <= 0) {
Log.e("TAG, "Failed to insert or replace row into " + uri);
} else {
// you are good...increment numInserted
numInserted++;
}
}
db.setTransactionSuccessful();
getContext().getContentResolver().notifyChange(uri, null);
} finally {
db.endTransaction();
}
return numInserted;

Batch insert/update via contentProvider based on 2 fields

i'm creating a contentProvider , and i wish to be able to send it multiple DB records (contentValues) to be inserted or updated to a single table using a single batch operations .
how do i do that?
batchInsert is intended only for inserting , but wouldn't it mean that insertion of something that already exists won't do anything?
also , is there a way for the update operation to use a special constraint ? for example , i need to ignore the primary key and update based on 2 other fields that together are unique.
"batchInsert is intended only for inserting" : this is true BUT you can override it in your ContentProvider to perform an UPSERT (insert/update) depending on the URI passed to batchInsert.
The following is some working code that I currently use to perform bulk inserts on time-series data (admittedly, I just delete anything that gets in the way instead of updating, but you could easily change this to your own ends.).
Also note the use of the sql transaction; this speeds up the process immensely.
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
switch (match(uri)) {
case ONEPROGRAMME:
String cid = uri.getLastPathSegment();
int insertCount = 0;
int len = values.length;
if (len > 0) {
long start = values[0].getAsLong(Programme.COLUMN_START);
long end = values[len - 1].getAsLong(Programme.COLUMN_END);
String where = Programme.COLUMN_CHANNEL + "=? AND " + Programme.COLUMN_START + ">=? AND "
+ Programme.COLUMN_END + "<=?";
String[] args = { cid, Long.toString(start), Long.toString(end) };
//TODO use a compiled statement ?
//SQLiteStatement stmt = sqlDB.compileStatement(INSERT)
sqlDB.beginTransaction();
try {
sqlDB.delete(tableName(PROGRAMME_TABLE), where, args);
for (ContentValues row : values) {
if (sqlDB.insert(tableName(PROGRAMME_TABLE), null, row) != -1L) {
insertCount++;
}
}
sqlDB.setTransactionSuccessful();
} finally {
sqlDB.endTransaction();
}
}
if (insertCount > 0)
getContext().getContentResolver().notifyChange(Resolver.PROGRAMME.uri, null);
return insertCount;
default:
throw new UnsupportedOperationException("Unsupported URI: " + uri);
}
}

Categories

Resources