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.
Related
Hi i'm still new in android and SQLite. I got activity which that can add query to my attached db file.
The problem is, i can't add data using my methods. Is there a simple methods to check if query is exists ?
Here is my DB Access
public class DBAccess {
private SQLiteOpenHelper openHelper;
private SQLiteDatabase db;
private static DBAccess instance;
Cursor c = null;
private DBAccess(Context context)
{
this.openHelper = new DatabaseOpenHelper(context);
}
public static DBAccess getInstance(Context context)
{
if(instance==null)
{
instance=new DBAccess(context);
}
return instance;
}
public void open()
{
this.db = openHelper.getWritableDatabase();
}
public void close()
{
if(db!=null)
{
this.db.close();
}
}
public void tambah(String a,String b)
{
String query= ("insert into TabelAbjad (kata,arti) values('"+a+"','"+b+"')");
db.execSQL(query);
}
public boolean checkdata(String c, String d)
{
String s;
String query= ("select kata from TabelAbjad where kata = '"+c+"' AND kata = '"+d+"'");
db.execSQL(query);
return true;
}
Here is when i try to call the methods
private void adddata()
{
DBAccess dbAccess = DBAccess.getInstance(getApplicationContext());
dbAccess.open();
String k = editkata.getText().toString().trim();
String a = editarti.getText().toString().trim();
if (dbAccess.checkdata(k,a))
{
Toast.makeText(this, "Data already exists",Toast.LENGTH_LONG).show();
dbAccess.close();
finish();
}
else
{
dbAccess.tambah(k,a);
dbAccess.close();
Toast.makeText(this, "Data Saved",Toast.LENGTH_LONG).show();
}
}
P.S : I'm call the method in button
You are missing a semicolon (;) in your query string.
Try this String query= ("select kata from TabelAbjad where kata = '"+c+"' AND kata = '"+d+"';"); for all your query strings
Your issue is that no row in the table will exist where the column kata has the value c as well as (AND) the value d it is impossible as a column can only have a single value, thus no rows would ever be extracted.
Perhaps you want to find rows that have either c OR d
in which case you could use :-
String query= ("select kata from TabelAbjad where kata = '"+c+"' OR kata = '"+d+"'");
i.e. AND has been changed to OR
Furthermore, execSQL cannot return a result, as per :-
Execute a single SQL statement that is NOT a SELECT or any other SQL
statement that returns data. execSQL
You thus need to either use the rawQuery method or the convenience query method, the latter recommended unless it's limitations mean that it cannot be used. As such you should try using something along the lines of (OR instead of AND assumed) :-
public boolean checkdata(String c, String d)
boolean rv = false;
String whereargs = "kata=? OR kata=?"; // the WHERE clause less the WHERE keyword ? for the arguments (used on a 1 for 1 basis)
String[] whereargs = new String[]{c,d}; // The arguments that will be substituted into the SQL
String[] columns = new String[]{"kata"};
Cursor csr = db.query("TabelAbjad",columns,whereclause,whereargs,null,null,null);
if (csr.getCount() > 0) {
rv = true;
}
csr.close();
return rv;
}
Although this may appear to be more complex to code it has advantages as it build the underlying SQL, it protects against SQL injection and it correctly encloses/escapes (wraps the values in single quotes etc) the values)arguments
You retrieve data (rows) into a Cursor (in this case you simply want to know if any rows matched the given criteria so getCount() is used (it returns the number of rows extracted which could be 0 if none)).
Is there a simple methods to check if query is exists ?
I believe that what you really mean is there anyway to check if the query returned any results.
As said above a query returns data via a Cursor (like a table but according to the query e.g. the Cursor above will consist of a number of rows (0 or more) with a single column named kata (i.e. the query is SELECT **kata** FROM .....) ) and thus you need to use a suitable method.
You can check/access numerous aspects/properties. Typically you'd move around the Cursor (e.g. while (your_cursor.moveToNext) {.... do things ....} can be used traverse all rows in the cursor). Cursor.
Once positioned appropriately at a row then you can use the get????(column_offset) to retrieve the data that came from the database (where column_offset is an integer with 0 representing the first column, however it is generally much wiser to retrieve the actual offset using the getColumnIndex(column_name_as_a_string method.))
So assuming that you wanted the data (String) in the first row that was extracted above into the Cursor csr then you could use :-
if (csr.moveToFirst()) {
String mykata = csr.getString(csr.getColumnIndex("kata"));
}
csr.close(); // You should always close a cursor when done with it.
I'm creating a forum application and I currently if I delete a thread I'm deleting all threads.
Is there a good method or query to check if the UserId == ThreadId?
My current code:
public void deleteThread() {
SQLiteDatabase db = this.getWritableDatabase();
// Delete All Rows
db.delete(TABLE_THREAD, null, null);
db.close();
Log.d(TAG, "Deleted all Thread info from sqlite");
}
You need to pass correct value to the well-documented delete method to narrow down the scope of deletion to a subset of all entries in the DB table.
public void deleteThreadById(String threadId) {
SQLiteDatabase db = this.getWritableDatabase();
String whereClause = "threadId = " + threadId;
db.delete(TABLE_THREAD, whereClause, null);
db.close();
}
Deleting all threads of a given user via their userId would be similar but probably doesn't make sense in a forum software.
This is how SQL works in general and it's a bit scary you started development without familiarising yourself with the very basics.
Something like this;
public void deleteThread(String threadName) {
SQLiteDatabase db = this.getWritableDatabase();
try {
db.delete(MYDATABASE_TABLE, "name = ?", new String[]{threadName});
} catch (Exception e) {
e.printStackTrace();
} finally {
db.close();
}
}
Something long these lines, querying database to find the specific row that has column which matches the parameter.
For example to delete a row which the name column is "Hello World";
deleteThread("Hello World");
How can i get the autoincrement value in thansaction body?
Code
public void insertAllStudents(List<Student> students) {
String sql = "INSERT INTO "+ StudentEntry.TABLE_NAME +" VALUES (?,?,?,?,?);";
SQLiteDatabase db = this.getWritableDatabase();
SQLiteStatement statement = db.compileStatement(sql);
db.beginTransaction();
for (Student student: students) {
statement.clearBindings();
statement.bindString(2, student.getId());
statement.bindString(3, student.getFirstName());
statement.bindString(4, student.getLastName());
statement.bindLong(5, student.getBirthday());
statement.execute();
}
db.setTransactionSuccessful();
db.endTransaction();
}
The first column (_ID) is autoincrement field. Is it opportunity to get this value?
student.getId() -that's not id from database, that's different id.
If you change your code to use db.insert(), this method returns the id of the inserted row - see Get generated id after insert.
There is also a specialised SQLite function to get the last inserted row if you'd prefer to keep compiling statements, see Best way to get the ID of the last inserted row on SQLite
edit: example using db.insert(). This isn't tested but should be pretty close to functional.
db.beginTransaction();
boolean success = true;
final ContentValues values = new ContentValues();
for (final Student student: students) {
values.put("student_id", student.getId());
values.put("first_name", student.getFirstName());
values.put("last_name", student.getLastName());
values.put("birthday", student.getBirthday());
final long id = db.insert("my_table", null, values);
if (id == -1) {
success = false;
break;
}
// TODO do your thing with id here.
}
if (success) {
db.setTransactionSuccessful();
}
db.endTransaction();
Instead of statement.execute(), you can do statement.executeInsert(). This returns the row ID of the inserted row. Or, as #Tom suggested, you can use db.insert() instead, and it will also return the inserted row ID. Using a compiled statement like you are doing now is faster though.
If you want to try the db.insert() approach, it would look something like this:
ContentValues values = new ContentValues();
for (Student student: students) {
// use whatever constants you have for column names instead of these:
values.put(COLUMN_STUDENT_ID, student.getId());
values.put(COLUMN_STUDENT_FIRSTNAME, student.getFirstName());
values.put(COLUMN_STUDENT_LASTNAME, student.getLastName());
values.put(COLUMN_STUDENT_BIRTHDAY, student.getBirthday());
db.insert(StudentEntry.TABLE_NAME, null, values);
}
ok I just followed an instruction that I should do this to retrieve sql data from database but it just cuts to there so far I have this inside my databasehelper class.
public void getIconResource(String tblName)
{
SQLiteDatabase db = this.getReadableDatabase();
String getresource = "Select * from " + tblName;
Cursor cursor = db.rawQuery(getresource,null); //null for conditions
if(cursor.moveToFirst())
{
do
{
int resource = cursor.getInt(3);
}
while (cursor.moveToNext());
}
db.close();
}
So somehow this does is it get all the values of my tables 4th column which contains an int... how do I retrieve the value in my MainActivity and save it in an array of integers?
just add everything in a ArrayList and return the arraylist
simply call the method in your main activty
public ArrayList<Integer> getIconResource(String tblName)
{
SQLiteDatabase db = this.getReadableDatabase();
String getresource = "Select * from " + tblName;
Cursor cursor = db.rawQuery(getresource,null); //null for conditions
ArrayList data= new ArrayList<>();
if(cursor.moveToFirst())
{
do
{
int resource = cursor.getInt(3);
data.add(resource);
}
while (cursor.moveToNext());
}
db.close();
}
return data;
}
Well, as you have it, the variable resource is scoped only to the while loop. Even if it wasn't it would constantly get overwritten on each loop iteration.
Instead, you should declare a collection higher up and Add each value to it during your while loop. You could also redefine your function to return the collection if integers.
public List<int> getIconResource(String tblName)
{
SQLiteDatabase db = this.getReadableDatabase();
List<int> myVals = new List<int>();
String getresource = "Select * from " + tblName;
Cursor cursor = db.rawQuery(getresource, null); //null for conditions
if (cursor.moveToFirst())
{
do
{
myVals.Add(cursor.getInt(3));
}
while (cursor.moveToNext());
}
db.close();
return myVals;
}
Also, as a note... string concatenation of a SQL query is a recipe for disaster. Look up SQL Injection and best practices to avoid it before continuing further. It is worth the time to get into good habits early on.
EDIT / ADDENDUM
Unless you also limit your result set returned from your table query, you will be getting every record. The function you have here really has no practical use and would likely cause more problems than any benefits it may have. I would suggest, as an example of a more usable function that returns a specific IconResource based on the IconId:
public int getIconResource(int iconId)
{
SQLiteDatabase db = this.getReadableDatabase();
String getresource = "select IconResource from IconTable where IconId = ?";
PreparedStatement pstmnt = db.prepareStatement(getresource);
pstrmnt.setString(1, iconId);
ResultSet rset = db.executeQuery();
int iconResource;
if (rset.next())
iconResource = rset.getInt("IconResource");
db.close();
return iconResource;
}
Of course, the above is making assumptions of your table structure.
Using the above, in your code elsewhere, you would simply call this function with the IconId and use the output however needed:
int iconResource = getIconResource(5); // returns the IconResource for IconId = 5
The above prevents any possible SQL Injection attacks by using a parameterized query and avoiding the use of dynamic concatenated strings sent to your SQL server.
You may try out the following code:
public List<Integer> getIconResource(String tblName)
{
List<Integer> list = new ArrayList<Integer>();
list.clear();
SQLiteDatabase db = this.getReadableDatabase();
String getresource = "Select * from " + tblName;
Cursor cursor = db.rawQuery(getresource,null); //null for conditions
if(cursor.moveToFirst())
{
do
{
int resource = cursor.getInt(3);
list.add(resource);
}
while (cursor.moveToNext());
}
db.close();
return list;
}
Then call this method in MainActivity and store the List in another Integer type list.
databasehelper dbhelper;
List<Integer> newList = dbhelper.getIconResource("Your tablename");
fot(int i = 0 ; i< newList.size() ; i++){
int yourValue = newList(i);
}
Currently I'm using ContentProvider in my application. Because of "layers" and no actual need for provider - I'm working on optimizing data access as much as possible. Here is my attempt to do this:
public static String getPreferenceString(Context context, String key)
{
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getReadableDatabase();
SQLiteStatement statement = database.compileStatement("SELECT Value FROM Preferences WHERE Key='" + key + "' LIMIT 1");
try
{
return statement.simpleQueryForString();
}
catch (Exception ex)
{
return "";
}
finally
{
statement.close();
database.close();
helper.close();
}
}
public static void setPreferenceString(Context context, String key, String value)
{
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getReadableDatabase();
SQLiteStatement statement = database.compileStatement("INSERT OR REPLACE INTO Preferences (Key, UpdatedOn, Value) VALUES ('" +
key + "', '" +
Utility.getDateConvertedToUTCDBString(new Date()) + "', '" +
value + "'); ");
try
{
statement.execute();
}
finally
{
statement.close();
database.close();
helper.close();
}
}
Is that about as close as I can get to direct calls to SQLite?
Should I have all this .close() statements in my code?
In setPreferenceString I did copy/paste and called getReadableDatabase even though I write data and it works. Why?
Is that about as close as I can get to direct calls to SQLite?
AFAIK SQL queries are closest you can go against RDBs
Should I have all this .close() statements in my code?
Personally, I would not create a DatabaseHelper, an SQLiteDatabase, and an SQLiteStatement each time I call that method. I would create all this just before you need them, and close them when no needed anymore. Also centralizing this is a good idea IMHO (using a singleton, for example).
Also your SQL statement could be written like
SELECT Value FROM Preferences WHERE Key= ? LIMIT 1
This way you only have to prepare it once and bind parameters as you need the statement. Same goes for any SQL query.