Java adding data to table using SQL - java

I am using an sql query to add data data to an existing database table.
I want to add data under the columns 'Room_Resource' and 'Quantity'.
The system is designed to allow bookings and i am trying to add bookings made to a tblBookings table, the code below is taken from JButton clicked function.
The value I want to add to Room_Resource is a name taken from a selected table within the system. I declared a variable for this 'resourceChosenString'
The value I want to add to quantity is from the 'Quantity' variable i have declared in relation to a combo box.
Here are my declarations:
int selectedResourceRow = tblResources.getSelectedRow();
Object resourceChosen = tblResources.getValueAt(selectedResourceRow,1);
String resourceChosenString = resourceChosen.toString();
int Quantity = cmbQuantity.getSelectedIndex();
I then have a sql statement:
String sql = ("INSERT INTO tblBookings (Room_Resource,Quantity) VALUES (" + resourceChosenString + " ', ' " + Quantity + " ',) ");
And then the execute code:
try{
pst = conn.prepareStatement(sql);
pst.execute();
JOptionPane.showMessageDialog(null, "Added");
} catch (Exception e){
JOptionPane.showMessageDialog(null, "Error Adding Booking");
}
Currently it gives me an error when I attempt to add the data to the table and wondered if anyone had any suggestions?
Also I considered that perhaps the problem could lie in the fact I have more than two columns in the external table and the table I am adding the data to so columns could be left blank. If this could be the problem, could anyone tell me how to get around it? Possibly if there is a null function I can use instead of values.

You probably want to tell us what database you're using and what error message you're getting. But just off the bat, it looks like your sql string is not formatted correctly. I don't know if you mistyped it in the question or if your code has a simple syntax error.
Just shooting from the hip with what you have, it looks like your sql statement should be:
String sql = "INSERT INTO tblBookings (Room_Resource,Quantity) VALUES ('" + resourceChosenString + "', " + Quantity + ")";
Notice that resourceChosenString should be wrapped in single quotes (you're missing the single quote on the left). Also, I don't think you're supposed to wrap a number in single quotes (I could be wrong since I don't know which database you're using).
Qwerky is right though; you should use a PreparedStatement.

The SQL you are generating is not valid and looks like this;
INSERT INTO tblBookings (Room_Resource,Quantity) VALUES (resource ', ' 1 ',)
^ ^
missing quote extraneous comma
You should tidy it up, or better still use a PreparedStatement.
String sql = "insert into tblBookings (Room_Resource,Quantity) values (?, ?)";
PreparedStatement pst = conn.prepareStatement(sql);
pst.setString(1, resourceChosenString);
pst.setInt(2, quantity); //variable names are not capitalised by convention
pst.execute();

Related

How to prevent SQL injection when the statement has a dynamic table name?

I am having code something like this.
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
Calculation of fullTableName is something like:
public String getFullTableName(final String table) {
if (this.schemaDB != null) {
return this.schemaDB + "." + table;
}
return table;
}
Here schemaDB is the name of the environment(which can be changed over time) and table is the table name(which will be fixed).
Value for schemaDB is coming from an XML file which makes the query vulnerable to SQL injection.
Query: I am not sure how the table name can be used as a prepared statement(like the name used in this example), which is the 100% security measure against SQL injection.
Could anyone please suggest me, what could be the possible approach to deal with this?
Note: We can be migrated to DB2 in future so the solution should compatible with both Oracle and DB2(and if possible database independent).
JDBC, sort of unfortunately, does not allow you to make the table name a bound variable inside statements. (It has its reasons for this).
So you can not write, or achieve this kind of functionnality :
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
And have TUSER be bound to the table name of the statement.
Therefore, your only safe way forward is to validate the user input. The safest way, though, is not to validate it and allow user-input go through the DB, because from a security point of view, you can always count on a user being smarter than your validation.
Never trust a dynamic, user generated String, concatenated inside your statement.
So what is a safe validation pattern ?
Pattern 1 : prebuild safe queries
1) Create all your valid statements once and for all, in code.
Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");
If need be, this creation itself can be made dynamic, with a select * from ALL_TABLES; statement. ALL_TABLES will return all the tables your SQL user has access to, and you can also get the table name, and schema name from this.
2) Select the statement inside the map
String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);
See how the unsafeUserContent variable never reaches the DB.
3) Make some kind of policy, or unit test, that checks that all you statementByTableName are valid against your schemas for future evolutions of it, and that no table is missing.
Pattern 2 : double check
You can 1) validate that the user input is indeed a table name, using an injection free query (I'm typing pseudo sql code here, you'd have to adapt it to make it work cause I have no Oracle instance to actually check it works) :
select * FROM
(select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?
And bind your fullName as a prepared statement variable here. If you have a result, then it is a valid table name. Then you can use this result to build a safe query.
Pattern 3
It's sort of a mix between 1 and 2.
You create a table that is named, e.g., "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all tables that are fit for deletion.
Then you make your validation step be
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
If this has a result, then you execute the safe_table_name. For extra safety, this table should not be writable by the standard application user.
I somehow feel the first pattern is better.
You can avoid attack by checking your table name using regular expression:
if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
}
It's impossible to inject SQL using such a restricted set of characters.
Also, we can escape any quotes from table name, and safely add it to our query:
fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
StringEscapeUtils comes with Apache's commons-lang library.
I think that the best approach is to create a set of possible table names and check for existance in this set before creating query.
Set<String> validTables=.... // prepare this set yourself
if(validTables.contains(fullTableName))
{
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
//and so on
}else{
// ooooh you nasty haker!
}
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;
N
10
create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;
TNAME
MYTAB
create or replace procedure deltab(v in varchar2)
is
LvSQL varchar2(32767);
LvChk number;
begin
LvChk := 0;
begin
select count(1)
into LvChk
from TABS2DEL
where tname = v;
if LvChk = 0 then
raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
end if;
exception when others
then raise;
end;
LvSQL := 'delete from '||v||' where n = 10';
execute immediate LvSQL;
commit;
end deltab;
begin
deltab('MYTAB');
end;
select * from mytab;
no rows found
begin
deltab('InvalidTableName');
end;
ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721

SQL variables in queries in Java

I want to use several times the same values.
If I use in dbForge for MySQL next query,
SET #v1 = 123, #v2='2014-04-11', #v3 = 'user1', #v4='title1';
INSERT INTO test_table (TID, CREATED, OWNER, TITLE)
VALUES (#v1,#v2,#v3,#v4)
ON DUPLICATE KEY UPDATE
CREATED=#v2, OWNER=#v3, TITLE=#v4
it correctly executes, but in Java, when I use code
final String dbQuerry = "SET #v1 = %s, #v2='%s', #v3 = '%s', #v4='%s';\n"+
"INSERT INTO test_table (TID, CREATED, OWNER, TITLE)\n" +
"VALUES (#v1,#v2,#v3,#v4)\n" +
"ON DUPLICATE KEY UPDATE\n" +
"CREATED=#v2, OWNER=#v3, TITLE=#v4";
String currentQuerry = String.format(dbQuerry, t.getParam("ID"),
t.getParam("Date"),
t.getParam("User"),
t.getParam("Title"));
mDBStatement.execute(currentQuerry);
I have an exception
SQL Exception: You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the right syntax to
use near 'INSERT INTO test_table (TID, CREATED, OWNER, TITLE) VALUES
(#v1,#v2,#v3,#v4) ON ' at line 2
I can use something like this
final String dbQuerry = "INSERT INTO test_table (TID, CREATED, OWNER, TITLE)\n" +
"VALUES (?,?,?,?)\n" +
"ON DUPLICATE KEY UPDATE\n" +
"CREATED=?, OWNER=?, TITLE=?";
PreparedStatement st = mDBConnection.prepareStatement(dbQuerry);
st.setInt(1, Integer.valueOf(t.getParam("ID")));
st.setString(2, t.getParam("Date"));
st.setString(5, t.getParam("Date"));
st.setString(3, t.getParam("User"));
st.setString(6, t.getParam("User"));
st.setString(4, t.getParam("Title"));
st.setString(7, t.getParam("Title"));
But it looks ugly.
Is there is a way to solve this problem?
One option is to use the special VALUES() function to reference the value that would have been inserted into a column, if the INSERT had succeeded, like this:
...
ON DUPLICATE KEY
UPDATE CREATED = VALUES(CREATED)
, OWNER = VALUES(ONWER)
, TITLE = VALUES(TITLE)
The latter form in your example is preferred, using placeholders for the bind variables. What's ugly is having to supply the same value twice.
I'd recommend something like this:
final String dbQuerry = "INSERT INTO test_table (TID,CREATED,OWNER,TITLE)\n" +
" VALUES (?,?,?, ?)\n" +
" ON DUPLICATE KEY UPDATE\n" +
" CREATED=VALUES(CREATED), OWNER=VALUES(OWNER), TITLE=VALUES(TITLE)";
PreparedStatement st = mDBConnection.prepareStatement(dbQuerry);
st.setInt(1, Integer.valueOf(t.getParam("ID")));
st.setString(2, t.getParam("Date"));
st.setString(3, t.getParam("User"));
st.setString(4, t.getParam("Title"));
And that's not ugly. That's the normative pattern.
Using the special VALUES() function is especially useful if we're upserting more than one row, either with a VALUES clause e.g.
INSERT INTO fee (fi, fo, fum)
VALUES
(1,'doo','dah'),(2,'menom','menah'),(3,'buhdeep','uhdeepee')
ON DUPLICATE KEY
UPDATE fo = VALUES(fo)
, fum = VALUES(fum)
Or, with an INSERT ... SELECT form:
INSERT INTO fee (fi, fo, fum)
SELECT t.ay, t.bee, t.cee FROM sometable t
ON DUPLICATE KEY
UPDATE fo = VALUES(fo)
, fum = VALUES(fum)
BTW... the error being returned from the first form is the type of error we'd expect if allowMultiQueries=true is not included in the connect string. Note that enabling multiple queries per execution effectively disables a security feature.
Consider carefully the SQL text that would be generated and sent to the database with some carefully crafted values:
val = "foo'; DROP TABLE students; --"
Using a prepared statement (with static SQL text with placeholder for bind variables, as in the example above) prevents this mode of SQL injection. And disallowing multiple statements in a single execution is another way to thwart SQL injection attacks.
I believe the # variables are used in stored procedures only...
Either you define a stored procedure or you can use the second option :
final String dbQuerry = "INSERT INTO test_table (TID, CREATED, OWNER, TITLE)\n" +
"VALUES (?,?,?,?)\n" +
"ON DUPLICATE KEY UPDATE\n" +
"CREATED=?, OWNER=?, TITLE=?";
PreparedStatement st = mDBConnection.prepareStatement(dbQuerry);
st.setInt(1, Integer.valueOf(t.getParam("ID")));
st.setString(2, t.getParam("Date"));
st.setString(5, t.getParam("Date"));
st.setString(3, t.getParam("User"));
st.setString(6, t.getParam("User"));
st.setString(4, t.getParam("Title"));
st.setString(7, t.getParam("Title"));

Why would a PreparedStatement with no input parameters succeed, but an identical version WITH input parameters fail?

I'm running the following code:
// Query without parameters
String query = "select data_source from Qc_data_blending_sources where external_object_type_name='well' AND instance_surrogate_key='837410' and attribute_name='preferred_latitude'";
PreparedStatement testPstmt = con.prepareStatement(query);
ResultSet testRs = testPstmt.executeQuery();
if(testRs.next()){
System.out.println("Result: " + testRs.getString(1));
}
// Query with parameters
sources_query = new StringBuilder("select data_source from Qc_data_blending_sources where external_object_type_name=? AND instance_surrogate_key=? AND attribute_name=?");
sourcesPstmt = con.prepareStatement(sources_query.toString());
sourcesPstmt.setString(1, vo.getWellSurrogateKey()); //set to 837410
sourcesPstmt.setString(2, "well");
sourcesPstmt.setString(3, "preferred_latitude");
dataBlendingSources.append("Preferred latitude: ");
sourcesRs = sourcesPstmt.executeQuery();
if(sourcesRs.next()){
dataBlendingSources.append(sourcesRs.getString(1) + " \n");
}
else{
dataBlendingSources.append(" not found, \n");
System.out.println("Preferred latitude not found. Query: " +
sources_query.toString() +
" \ninstance_surrogate_key: " + vo.getWellSurrogateKey() +
" attribute_name: preferred_latitude");
}
When I run it, it gives a valid result for the first query and says "preferred latitude not found" for the second one. What gives? What could be going on here?
Looks like you are using setString() entirely, but you may need to use setInt() if the column type is not a string for the surrogate key, for example.
Per prepared statement documentation:
Note: The setter methods (setShort, setString, and so on) for setting
IN parameter values must specify types that are compatible with the
defined SQL type of the input parameter. For instance, if the IN
parameter has SQL type INTEGER, then the method setInt should be
used.
Totally off the wall guess (I could be sure if I wrote code to test it, but I'm too lazy):
You call con.prepareStatement() twice on the same connection. I suggest closing the first prepareStatement before getting another prepareStatement from the database. they may be interfering with each other. I would also consider closing the resultSet too when I'm done with it.
coding suggestion: If you aren't already doing so, I suggest closing a connection, preparedStatement, and resultSet in the finally block of a try/catch/finally, in the reverse order you obtain them (checking for null before closing each).

Column ' ' in field is ambiguous

Im working with MySQL in java.
I'm trying to update the 'owners' field in one of the tables 'regApartmentsTable', 'gardApartmentsTable', 'penthousesTable', which is empty, and corresponds to specific apartmentNum and street, and replace it with the string 'newOwners'.
In order to do that I've wrote the following code:
st=connection.prepareStatement("UPDATE regApartmentsTable,gardApartmentsTable,penthousesTable SET owners=? " +
"WHERE owners=? AND apartmentNum=? AND street=?");
st.setString(1, newOwners);
st.setString(2, "");
st.setInt(3, apartmentNum);
st.setString(4, streetName+" "+buildingNum);
I include the 3 tables since I need to look in all of them. (The required apartment, which has no owners, and matches the apartmentNum and street, cannot be in more than one table, if it helps anyone).
But, when I try to run this code, I get a "Column 'owners' in field is ambiguous" error.
Any ideas how else should I write the SQL command ?
thanks ahead!
EDIT:
I didn't get a sufficient answer to my problem... Ok, I understood that the exception raises since 'owners' field is common in those three tables.
And yet, how do I solve the problem? I cannot add a prefix with the table's name since I do not know in which table I'm going to find the required apartment... If I knew so, I wouldn't have searched in 3 tables.
The multiple tables UPDATE in MySQL is just a form of table join, using regApartmentsTable.owners and such.
You need a separate UPDATE for every table here, as a join is not what you intend for the update. Or make a base table.
str = connection.prepareStatement("UPDATE regApartmentsTable SET owners=? " +
"WHERE owners=? AND apartmentNum=? AND street=?");
str.setString(1, newOwners);
str.setString(2, "");
str.setInt(3, apartmentNum);
str.setString(4, streetName+" "+buildingNum);
str = connection.prepareStatement("UPDATE gardApartmentsTable SET owners=? " +
"WHERE owners=? AND apartmentNum=? AND street=?");
stg.setString(1, newOwners);
stg.setString(2, "");
stg.setInt(3, apartmentNum);
stg.setString(4, streetName+" "+buildingNum);
stp = connection.prepareStatement("UPDATE penthousesTable SET owners=? " +
"WHERE owners=? AND apartmentNum=? AND street=?");
stp.setString(1, newOwners);
stp.setString(2, "");
stp.setInt(3, apartmentNum);
stp.setString(4, streetName+" "+buildingNum);

problem with prepare statement

preparedStatement = connection.prepareStatement("select fname,lname, "
+ "sportman_code,start,finish,salary,amount,number,pnumber "
+ "from sportman,customer "
+ "where customer.customer_code = "
+ "sportman.customer_code order by ? limit ?,?");
preparedStatement.setString(1, "fname");
preparedStatement.setInt(2, 0);
preparedStatement.setInt(3, 9);
resultSet = preparedStatement.executeQuery();
order by didn't work.
why?
when i put fname instead ? it work correctly.
"sportman.customer_code order by fname limit ?,?");
how can i do that?
Your ORDER BY works, but not as you expect it to. When you use
preparedStatement.setString(1, "fname");
it will make an ORDER BY like this
ORDER BY 'fname'
and not as you expect
ORDER BY fname
The code in your question will then be like sorting a package of M&Ms alphabetically
You can't bind in identifiers like table names or column names, only values that you want to insert, compare, etc
Binding works for literals in the query, not for keywords or identifiers. You'll need to use another approach for sanitizing the sort field if you want it to be dynamic.

Categories