So I have a database, SQLiteDatabase db I am writing a couple private methods in my manager class that will be called by a public method:
public void updateData (MakeabilityModel newData){
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
try {
reWriteSVTable(db, list);
db.setTransactionSuccessful();
} catch (Exception e){
//TODO through rollback message?
e.printStackTrace();
} finally {
db.endTransaction();
}
}
//Private Methods
private void clearTable(SQLiteDatabase db, String table){
db.delete(table, null, null);
}
private void reWriteSVTable(SQLiteDatabase db, List<MakeabilityLens> lenses){
clearTable(db, singleVision);
ContentValues cv;
for(int i=0; i<lenses.size(); i++){
cv = new ContentValues();
cv.put(colScreenID, hsID);
cv.put(colIconID, id);
cv.put(colRank, hsTotal);
db.insert(isLookUp, colID, cv);
}
}
My question is this.. i want to be able to throw sql exceptions back to the public method so that if there is an exception, it will kill the transaction and rollback ALL data..
it appears that using delete() and insert() methods are cleaner than execSQL() but don't throw sqlExceptions. execSQL() on the other hand does? do i need to uses execSQL and how do I insure that should it throws an exception in any of the private methods that it will catch it and roll it back in the private method
first of all execSQL() throws an exception if the sql string is not valid. that is the exception is on the sql string syntax NOT the sql operation. that is, it will not throw an exception if the sql statement is valid but the operation failed (because of a constraint for example).
So ..
basically the only difference between execSQL() and delete() is that delete() returns the number of rows affected (in your case, the number of deleted rows), but execSQL() doesn't.
Note:
for delete() to return the number of rows affected, you have to pass any value other than null in the where clause parameter. In your case, pass "1".
Related
I have a method in my MainActivity resetSortIndexes that runs a save() in the model class that runs an SQLite database "execSQL()" method. Now I've read that I should not be using execSQL() to avoid SQL injection attacks and that I should not be using rawQuery() for any INSERT operation. So should I use ContentValues() and insert()?
MainActivity.java
...
public static void resetSortIndexes() {
int index = allList.size();
for (ListItem s : allList) {
s.setSortorder(index);
s.save(sqLiteDB);
index--;
}
}
ListItem.java
...
public void save(SQLiteDB helper){
String sql = "INSERT OR REPLACE INTO " + TABLE_NAME + "(_id,type,typecolor,todo,note1,note2," +
"duedatentime,timestamp,notiftime,notiftime2,randint,sortorder,listone,listtwo," +
"listthree,listfour,listfive,listsix,listseven,listeight,listnine,listten,listeleven," +
"listtwelve,listthirteen,listfourteen,listfifteen,listsixteen,listseventeen," +
"listeighteen,listnineteen,listtwenty) VALUES" +
"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
// The object parameters from the ListItem class.
Object[] params = new Object[]{_id,_type,_typecolor,_todo,_note1,_note2,_duedatentime,
_timestamp,_notiftime,_notiftime2,_randint,_sortorder,_listone,_listtwo,
_listthree,_listfour,_listfive,_listsix,_listseven,_listeight,_listnine,
_listten,_listeleven,_listtwelve,_listthirteen,_listfourteen,_listfifteen,
_listsixteen,_listseventeen,_listeighteen,_listnineteen,_listtwenty};
// A method in the SQLiteDB class.
helper.executeQuery(sql,params);
}
SQLiteDB.java
...
public void executeQuery(String sql, Object[] params) {
SQLiteDatabase db = getReadableDatabase();
db.beginTransaction();
try {
**db.execSQL(sql, params);**
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if(db.isOpen()) {
db.close();
}
}
You can use the method insertWithOnConflict(TABLE_NAME,null,contentvalues,SQLiteDatabase.CONFLICT_REPLACE);
Where contenvalues is a ContenValues populated using it's put(column_name,value) method for each value to be inserted.
The code would be along the lines of :-
ContentValues cv = new Contentvalues();
cv.put("_id",the_id);
cv.put("type",the_type);
..... etc
long result = helper.insertWithOnConflict(TABLE_NAME,null,cv,SQliteDatabase.CONFLICT_REPLACE);
result will be the rowid of the inserted row or -1.
insertWithOnConflict
CONFLICT_REPLACE
P.S. using execSQL as you have, would offer protection from SQL injection as the SQL itself is not subject to user input and the values are bound/passed as arguments.
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();
}
is it save to begin and commit a transaction by retrieving the database two times with getWriteableDatabase(). I have this scenario if I want to use two DAOs in one transaction
Something like this:
SqliteDatabase db = userDao.getWriteableDatabase();
try{
userDao.insert(firstname, lastname);
addressDao.insert(street);
// commit
db.setTransactionSuccessful();
db.endTransaction();
catch(Exception e){
// rollback
db.endTransaction();
}
Where:
class UserDao extends Dao {
public void insert(String firstname, String lastname){
SqliteDatabase db = getWriteableDatabase();
...
db.insertOrThrow(...);
}
}
class AdressDao extends Dao {
public void insert(String street){
SqliteDatabase db = getWriteableDatabase();
...
db.insertOrThrow(...);
}
}
getWriteableDatabase() is just a shortcut for SQLiteOpenHelper.getWriteableDatabase()
So is it safe to work in this way in a transaction or does getWriteableDatabase() returns another instance and therefore the transaction is no longer in the right scope?
If so i could pass the SqliteDatabase as parameter to the DAO, like
SqliteDatabase db = userDao.getWriteableDatabase();
try{
userDao.insert(db, firstname, lastname);
addressDao.insert(db, street);
// commit
db.setTransactionSuccessful();
db.endTransaction();
catch(Exception e){
// rollback
db.endTransaction();
}
getWritableDatabase() creates the instance the first time that gets called and cache the returned value. So you're not opening the db twice and yes, it's safe.
i am working on sqlite insertion using contentvalues with transaction . The following code does not generate any exception however the data is not inserted.
Did i miss somethings ? Thanks.
public boolean addRecord(Rec rec) {
SQLiteDatabase db = this.getWritableDatabase();
db.beginTransaction();
ContentValues values = new ContentValues();
values.put(KEY_ID, rec.get());
// Inserting Row
try {
db.insertOrThrow(TABLE_RECORDS, null, values);
} catch (SQLException e) {
e.printStackTrace();
}
db.endTransaction();
db.close();
return true;
}
After calling beginTransaction, you must call setTransactionSuccessful to ensure that the transaction gets committed. Without that call, any changes in the transaction are rolled back.
db.beginTransaction();
try {
...
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
This particular construction ensures that any exception in the actual database code will result in an rollback.
(If you get an exception, it is a bad idea to just swallow it; the entire function must fail.)
I'm trying to write a function that will delete every row in a given table but I'm getting a null pointer exception. Could somebody point me in the right direction? Here is the code...
public void deleteall(){
SQLiteDatabase db = tweets.getWritableDatabase();
String delete = "TRUNCATE FROM tweets";
db.rawQuery(delete, null);
}
Check if tweets is null.
I think it's more simpler to use this call, than using rawQuery.
Your rawQuery must be parsed, but using the delete method it uses already a parametrized query.
db.delete('tweets',null,null);
just to delete all rows, you can use following method.
void deleteAll()
{
SQLiteDatabase db= this.getWritableDatabase();
db.delete(table_name, null, null);
}
dataBaseHelper = new DataBaseHelper(getApplicationContext(), DataBaseHelper.DataBaseName, null, 1);
sqLiteDatabase = dataBaseHelper.getWritableDatabase();
if (sqLiteDatabase != null) {
sqLiteDatabase.delete(DataBaseHelper.TableName, null, null);
Toast.makeText(MainActivity.this, "Refresh", Toast.LENGTH_SHORT).show();
} else
Toast.makeText(MainActivity.this, "Error", Toast.LENGTH_SHORT).show();
break;
}
I was getting a null pointer exception, and then I realised that my method was not calling db.open() first. Added that (and db.close()) and it worked.
SQLite does not have any TRUNCATE statement, so you must do:
public void deleteall(){
SQLiteDatabase db = tweets.getWritableDatabase();
String delete = "DELETE FROM tweets";
db.rawQuery(delete, null);
}
The right answer is this:
db.delete('tweets',"1",null);
From Android official documentation: SQLiteDatabase