I need to have dynamic SQL which accepts table and column names from users and uses those in queries. Right now I do this with
public Object doSearch(String table, List<String> columns) {
//... some logic
String.format("SELECT %s from %s", String.join(", ", columns), table");
//... some execution and return
}
The source is NOT trusted, so I want to do a whitelist of table and column names, but that list changes. The list of valid tables is strictly the list of tables on my_schema and the list of valid columns is strictly the columns on that particular table.
I've searched around SO and gotten a solution that looks something like:
private boolean validate(String tableName, List<String> columnNames) throws SQLException {
return tableExist(tableName) && columnNames.stream().allMatch(cn -> columnExistsOnTable(tableName, cn));
}
private boolean tableExist(String tableName) throws SQLException {
try (ResultSet rs = connection.getMetaData().getTables(null, schema, tableName, null)) {
while (rs.next()) {
String tName = rs.getString("TABLE_NAME");
if (tName != null && tName.equals(tableName)) {
return true;
}
}
}
return false;
}
private boolean columnExistsOnTable(String tableName, String columnName) {
try (ResultSet rs = connection.getMetaData().getColumns(null, schema, tableName, columnName)) {
while (rs.next()) {
String tName = rs.getString("COLUMN_NAME");
if (tName != null && tName.equals(tableName)) {
return true;
}
}
} catch (SQLException sqle) {
return false;
}
return false;
}
Is this safe and correct?
For each of those methods, you could do this one time in an initialization method and cache the table/column names so you don't have to do a database check every time... something like this:
private Map<String, Set<String>> tableColNames = new HashMap();
private void initCache(){
// build the cache
// get all tables
// get all columns
// add tables and columns to the map
}
private boolean tableExist(String tableName) throws SQLException {
return tableColNames.containsKey(tableName);
}
private boolean columnExistsOnTable(String tableName, String columnName) {
if(tableExist(tableName)){
return tableColNames.get(tableName).contains(columnName);
} else {
return false;
}
}
// could make a method for checking a list of Strings too...
// return tableColNames.get(tableName).containsAll(columnName);
https://docs.oracle.com/javase/7/docs/api/index.html?java/sql/ResultSetMetaData.html
Related
I'm using PostgreSQL to save a form in three tables, one for the name and id, another one for the fields to populate, and a third to store values for the fields.
private static final String REMOVE_FORM = "DELETE FROM forms WHERE name = ?";
private PreparedStatement removeFormQuery;
public boolean connect() throws SQLException{
this.connection = DriverManager.getConnection(URL);
this.removeFormQuery = this.connection.prepareStatement(REMOVE_FORM);
}
public void close() throws SQLException {
if(null != this.connection) {
this.connection.close();
}
if(null != this.removeFormQuery) {
this.removeFormQuery.close();
}
}
private void removeForm(String form) {
try {
removeFormQuery.setString(1, form);
int execute = removeFormQuery.executeUpdate();
System.out.println(execute);
ResultSet generatedKeys = removeFormQuery.getGeneratedKeys();
while (generatedKeys.next()) {
int anInt1 = generatedKeys.getInt("id");
removeFieldQuery.setInt(1, anInt1);
if (removeFieldQuery.execute())
System.out.println("remove field failed");
else
System.out.println("remove field success");
ResultSet generatedFieldsKeys = removeFieldQuery.getGeneratedKeys();
while (generatedFieldsKeys.next()) {
int anInt = generatedFieldsKeys.getInt("id");
removeListItemQuery.setInt(1, anInt);
if (removeListItemQuery.execute())
System.out.println("remove listItem failed");
else
System.out.println("remove listItem success");
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
The code doesn't generate keys after the remove query is executed, am I missing something?
While you haven't told us what queries you are running, if they are deletes then you wouldn't expect any generated keys.
From the Javadoc for Statement.getGeneratedKeys():
* Retrieves any auto-generated keys created as a result of executing this
* Statement object. If this Statement object did
* not generate any keys, an empty ResultSet
* object is returned.
Keys may be auto generated when you insert into a table, creating a new row which needs a primary key, but when you delete a row no key generation occurs.
I'm having a problem using the BeanUtils.setProperty method.
I'm using this JAR:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
I run a MySQL query that returns one record and I'm mapping the resultset to a JavaBean that I've made.
Here you have the main class.
public class QueryTester {
public static void viewTable(Connection con) throws SQLException, InstantiationException, IllegalAccessException, InvocationTargetException {
Statement stmt = null;
String query = "SELECT * FROM Books WHERE code = 'AA00'";
try {
stmt = (Statement) con.createStatement();
ResultSet rs = stmt.executeQuery(query);
ResultSetMapper<Books> rsMapper = new ResultSetMapper<Books>();
List<Books> list = rsMapper.mapResultSetToObject(rs, Books.class);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (stmt != null) {
stmt.close();
}
}
}
public static void main(String[] args) {
Connection conn = null;
String url = "jdbc:mysql://localhost/dbname";
String driver = "com.mysql.jdbc.Driver";
String userName = "root";
String password = "root";
try {
Class.forName(driver).newInstance();
conn = (Connection) DriverManager.getConnection(url,userName,password);
viewTable(conn);
conn.close();
} catch (Exception e) {
System.out.println("NO CONNECTION");
}
}
}
And this is the method that uses the BeanUtils.setProperty method.
public class ResultSetMapper<T> {
public List<T> mapResultSetToObject(ResultSet rs, Class<T> outputClass) throws InstantiationException, SQLException, IllegalAccessException, InvocationTargetException {
List<T> outputList = new ArrayList<T>();
if (rs == null) {
return outputList;
}
if (!outputClass.isAnnotationPresent(Entity.class)) {
throw new InstantiationException("Entity notation not present.");
}
ResultSetMetaData rsmd = rs.getMetaData();
// retrieve data fields from output class
Field[] fields = outputClass.getDeclaredFields();
while (rs.next()) {
T bean = (T) outputClass.newInstance();
for (int iterator = 0; iterator < rsmd.getColumnCount(); iterator++) {
String columnName = rsmd.getColumnName(iterator + 1);
Object columnValue = rs.getObject(iterator + 1);
for (Field field : fields) {
if (field.isAnnotationPresent(Column.class)) {
Column column = field.getAnnotation(Column.class);
if (column.name().equalsIgnoreCase(columnName) && columnValue != null) {
BeanUtils.setProperty(bean, field.getName(), columnValue);
break;
}
}
}
}
outputList.add(bean);
}
return outputList;
}
}
mapResultSetToObject method returns a List with one element that is correct but the bean is set in a wrong way.
The fields code and bookDescription are set right but kPrice field is set null instead of 3.000 that is the value from database.
I run this code in debug mode and "columnValue" variable's value is 3.000 but the setProperty method doesn't set the right value and the value remains null.
Here you have my Java Bean.
#Entity
public class Books {
#Column(name="code")
private String code;
#Column(name="book_description")
private String bookDescription;
#Column(name="kPrice")
private BigDecimal kPrice;
public Books() {}
public Books(String code, String bookDescription, BigDecimal kPrice){
this.code = code;
this.bookDescription = bookDescription;
this.kPrice = kPrice;
}
/* Getters and setters */
...
}
And this is the MySQL table and the record.
CREATE TABLE `Books` (
`code` varchar(4) NOT NULL,
`book_description` varchar(50) NOT NULL DEFAULT '',
`kPrice` decimal(10,4) NOT NULL DEFAULT '1.0000',
PRIMARY KEY (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO dbname.Books (code, book_description, kPrice) VALUES('AA00', 'Description example', 3.0000);
Why I get this behaviour? What am I missing?
Thanks in advance
Are you sure which the name of setters/getters is the same of property?
In some case, the problem is that.
See my example below:
#Entity
public class Books {
#Column(name="code")
private String code;
#Column(name="book_description")
private String bookDescription;
#Column(name="kPrice")
private BigDecimal kPrice;
public Books() {}
public Books(String code, String bookDescription, BigDecimal kPrice){
this.code = code;
this.bookDescription = bookDescription;
this.kPrice = kPrice;
}
public void setKPrice ( Bigdecimal kPrice) // and not setkPrice or setPrice..
{
this.kPrice = kPrice;
}
public BigDecimal getKPrice () // and not getkPrice or getPrice..
{
return this.kPrice;
}
}
I want to create multi parameter search using JDBC prepared statement so as to prevent SQL injection attack and improve performance. As I couldn't find the best way to do it on the net.
I've tried to implement on my own as follow.
In this program I want to allow the user to search an employee by either first name, last name or department id.
I want to know
if my implementation would prevent SQL injection
If I am using prepared statement correctly? I have some doubt on this line
PreparedStatement stat = conn.prepareStatement(sql.toString());
Let's say two users search by using the same parameters, and thus it would result the same
sql string. According to my implementation, would the database have to prepare that sql twice or just once?
public class EmpDAO {
public static List<Employee> findByCriteria(Employee e)
throws SQLException, IOException {
try (Connection conn = getConnection()) {
StringBuilder sql = new StringBuilder("SELECT * FROM Employee ");
//collect user supplied parameters
Map<String, String> params = new HashMap<String, String>();
if (e.getFname() != null && e.getFname().length() != 0) {
params.put(EmpDAO.FNAME, e.getFname());
}
if (e.getLname() != null && e.getLname().length() != 0) {
params.put(EmpDAO.LNAME, e.getLname());
}
if (e.getDepid() > 0) {
params.put(EmpDAO.DEPT_ID, new Integer(e.getDepid()).toString());
}
//construct prepared statement based on the parameters
Set<String> colSet = params.keySet();
if (colSet != null && !colSet.isEmpty()) {
StringBuilder whereClause = new StringBuilder(" WHERE");
String andOp = "";
for (String colName : colSet) {
whereClause.append(andOp);
whereClause.append(" ");
whereClause.append(colName);
whereClause.append("=? ");
andOp = " AND ";
}
sql.append(whereClause);
}
PreparedStatement stat = conn.prepareStatement(sql.toString());
int paramPos = 1;
for (String colName : colSet) {
if (colName.equals(EmpDAO.FNAME)) {
stat.setString(paramPos, params.get(colName));
}
if (colName.equals(EmpDAO.LNAME)) {
stat.setString(paramPos, params.get(colName));
}
if (colName.equals(EmpDAO.DEPT_ID)) {
stat.setInt(paramPos, Integer.parseInt(params.get(colName)));
}
paramPos++;
}
List<Employee> emp1 = new ArrayList<>();
try (ResultSet result = stat.executeQuery()) {
while (result.next()) {
Employee emp = new Employee();
emp.setDepid(result.getInt("DEPT_ID"));
emp.setEmpid(result.getInt("EMP_ID"));
emp.setFname(result.getString("FNAME"));
emp.setJobid(result.getInt("JOB_ID"));
emp.setLname(result.getString("LNAME"));
emp.setMangid(result.getInt("MANAGER_EMP_ID"));
emp.setSalary(result.getInt("SALARY"));
emp1.add(emp);
}
}
return emp1;
}
}
public static Connection getConnection() throws SQLException, IOException {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return DriverManager.getConnection("jdbc:mysql://localhost:3306/test",
"root", "root");
}
public static final String FNAME = "FNAME";
public static final String LNAME = "FNAME";
public static final String DEPT_ID = "FNAME";
}
Yes -- as written this will prevent SQL injection attacks and yes, that is how you use .prepareStatement. You should probably post this code on https://codereview.stackexchange.com/ if you want a critique of how it's done.
I've ran into a problem of having to run a number of different queries on the DB (different return types, different number of columns, etc).
While writing that i started to wonder if there's a proper way of writing a helper function.
It seemed that it's really easy to write a function that returns a ResultSet.
However since it a) doesn't close connection b) doesn't close the result set it seems as a possibly working, but improper solution. Is there any place to dump in all results so that they can be returned safely.
(Only thing i could come up with, is just returning a 2D string array (after converting all data to strings) and then converting it all back)
EDIT : Sorry for not writing clear, was wondering if there's any way to just store the result of the query as is (don't need to modify it) without writing a separate method for every possible return type.
The idea behind a 2d string list is being able to store the query values as is.
Col1 Row1 | Col2 Row1 | Col3 Row1
Col1 Row2 | Col2 Row2 | Col3 Row2
EDIT 2 Thank you for replies, i guess i'll just write a small parser for it.
You shouldn't be returning resultSets, you should read the results from the resultset into some kind of container object. A ResultSet is a wrapper around a database cursor, it goes away when the connection closes. It's something you read from and close right away, not something you can pass around your application.
Look at how spring-jdbc does it. You implement a resultSetMapper that is passed to the method on the JdbcTemplate.
Several observations:
You don't need to use Spring to use spring-jdbc. However, I see very little value in reimplementing this stuff yourself.
It's not the job of the code that reads the ResultSet to open and close connections, that needs to be elsewhere.
I'd recommend looking at Spring JDBC. Don't write such a thing yourself. It's already been done, and quite well.
For example, I don't like your idea of returning a List of Strings. You lose a lot of info that way. I'd return a Map of Lists (column view) or List of Maps (row view).
If you must, here are some database utilities that would get you started.
package persistence;
import java.sql.*;
import java.util.*;
/**
* util.DatabaseUtils
* User: Michael
* Date: Aug 17, 2010
* Time: 7:58:02 PM
*/
public class DatabaseUtils {
/*
private static final String DEFAULT_DRIVER = "oracle.jdbc.driver.OracleDriver";
private static final String DEFAULT_URL = "jdbc:oracle:thin:#host:1521:database";
private static final String DEFAULT_USERNAME = "username";
private static final String DEFAULT_PASSWORD = "password";
*/
/*
private static final String DEFAULT_DRIVER = "org.postgresql.Driver";
private static final String DEFAULT_URL = "jdbc:postgresql://localhost:5432/party";
private static final String DEFAULT_USERNAME = "pgsuper";
private static final String DEFAULT_PASSWORD = "pgsuper";
*/
private static final String DEFAULT_DRIVER = "com.mysql.jdbc.Driver";
private static final String DEFAULT_URL = "jdbc:mysql://localhost:3306/party";
private static final String DEFAULT_USERNAME = "party";
private static final String DEFAULT_PASSWORD = "party";
public static void main(String[] args) {
long begTime = System.currentTimeMillis();
String driver = ((args.length > 0) ? args[0] : DEFAULT_DRIVER);
String url = ((args.length > 1) ? args[1] : DEFAULT_URL);
String username = ((args.length > 2) ? args[2] : DEFAULT_USERNAME);
String password = ((args.length > 3) ? args[3] : DEFAULT_PASSWORD);
Connection connection = null;
try {
connection = createConnection(driver, url, username, password);
DatabaseMetaData meta = connection.getMetaData();
System.out.println(meta.getDatabaseProductName());
System.out.println(meta.getDatabaseProductVersion());
String sqlQuery = "SELECT PERSON_ID, FIRST_NAME, LAST_NAME FROM PERSON ORDER BY LAST_NAME";
System.out.println("before insert: " + query(connection, sqlQuery, Collections.EMPTY_LIST));
connection.setAutoCommit(false);
String sqlUpdate = "INSERT INTO PERSON(FIRST_NAME, LAST_NAME) VALUES(?,?)";
List parameters = Arrays.asList("Foo", "Bar");
int numRowsUpdated = update(connection, sqlUpdate, parameters);
connection.commit();
System.out.println("# rows inserted: " + numRowsUpdated);
System.out.println("after insert: " + query(connection, sqlQuery, Collections.EMPTY_LIST));
} catch (Exception e) {
rollback(connection);
e.printStackTrace();
} finally {
close(connection);
long endTime = System.currentTimeMillis();
System.out.println("wall time: " + (endTime - begTime) + " ms");
}
}
public static Connection createConnection(String driver, String url, String username, String password) throws ClassNotFoundException, SQLException {
Class.forName(driver);
if ((username == null) || (password == null) || (username.trim().length() == 0) || (password.trim().length() == 0)) {
return DriverManager.getConnection(url);
} else {
return DriverManager.getConnection(url, username, password);
}
}
public static void close(Connection connection) {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void close(Statement st) {
try {
if (st != null) {
st.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void close(ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void rollback(Connection connection) {
try {
if (connection != null) {
connection.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static List<Map<String, Object>> map(ResultSet rs) throws SQLException {
List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
try {
if (rs != null) {
ResultSetMetaData meta = rs.getMetaData();
int numColumns = meta.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new HashMap<String, Object>();
for (int i = 1; i <= numColumns; ++i) {
String name = meta.getColumnName(i);
Object value = rs.getObject(i);
row.put(name, value);
}
results.add(row);
}
}
} finally {
close(rs);
}
return results;
}
public static List<Map<String, Object>> query(Connection connection, String sql, List<Object> parameters) throws SQLException {
List<Map<String, Object>> results = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = connection.prepareStatement(sql);
int i = 0;
for (Object parameter : parameters) {
ps.setObject(++i, parameter);
}
rs = ps.executeQuery();
results = map(rs);
} finally {
close(rs);
close(ps);
}
return results;
}
public static int update(Connection connection, String sql, List<Object> parameters) throws SQLException {
int numRowsUpdated = 0;
PreparedStatement ps = null;
try {
ps = connection.prepareStatement(sql);
int i = 0;
for (Object parameter : parameters) {
ps.setObject(++i, parameter);
}
numRowsUpdated = ps.executeUpdate();
} finally {
close(ps);
}
return numRowsUpdated;
}
}
You can write helper functions that parse a ResultSet and convert it into an ArrayList or an array or even the fields of an object. For instance, lets say you have a table of orders and then a query returns all of the rows of that table for a particular user (customer). We could then do something like this:
static List<Order> parseOrder(ResultSet rs) {
ArrayList<Order> orderList = new ArrayList<>();
while(rs.next() ) {
Order order = new Order();
order.setID(rs.getInt(1));
order.setCustomerID(rs.getInt(2));
order.setItemName(rs.getString(3));
orderList.add(order);
}
return orderList;
}
Simply turning the result set into an array of an array of Objects would be more general, but probably less useful.
I would leave it up to the calling function to close this ResultSet and possible the PreparedStatement (or Statement) and database connection.
I am trying to insert some rows in to a table... I am using postgressql-7.2.jar.
I get the following exception
org.postgresql.util.PSQLException: No results were returned by the query.
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:255)
I have already Googled and the possible reasons suggested are
Use executeUpdate() method or execute() method instead of executeQuery() method.
This could possibly be because of jar problem; try other versions of postgres jars.
In some places they save it could be because of heap space error.
I have tried all the three solutions but none of them work...
I am not pasting the code since I have just used statement.executeUpdate(queryString).
The insert statements load the data in to the table but still I get this error.
Can some one help me out in this?
What type of SQL statement are you trying to run with executeQuery()? It should not be an INSERT or UPDATE - these are not queries.
Without posting the actual SQL statement, code samples, or what the table looks like - it's pretty hard to actually help you with your problem. Without specifics all we can do is guess.
This code works perfectly for me running PostgreSQL 8.1 and its driver. Perhaps it can be a template for finding what's wrong with yours.
You need a single table named PERSON with columns PERSON_ID, FIRST_NAME, LAST_NAME. I made PERSON_ID the auto incremented primary key.
package persistence;
import java.sql.*;
import java.util.*;
public class DatabaseUtils
{
private static final String DEFAULT_DRIVER = "org.postgresql.Driver";
private static final String DEFAULT_URL = "jdbc:postgresql://localhost:5432/party";
private static final String DEFAULT_USERNAME = "pgsuper";
private static final String DEFAULT_PASSWORD = "pgsuper";
public static void main(String[] args)
{
String driver = ((args.length > 0) ? args[0] : DEFAULT_DRIVER);
String url = ((args.length > 1) ? args[1] : DEFAULT_URL);
String username = ((args.length > 2) ? args[2] : DEFAULT_USERNAME);
String password = ((args.length > 3) ? args[3] : DEFAULT_PASSWORD);
Connection connection = null;
try
{
connection = createConnection(driver, url, username, password);
DatabaseMetaData meta = connection.getMetaData();
System.out.println(meta.getDatabaseProductName());
System.out.println(meta.getDatabaseProductVersion());
String sqlQuery = "SELECT PERSON_ID, FIRST_NAME, LAST_NAME FROM PERSON ORDER BY LAST_NAME";
System.out.println("before insert: " + query(connection, sqlQuery, Collections.EMPTY_LIST));
connection.setAutoCommit(false);
String sqlUpdate = "INSERT INTO PERSON(FIRST_NAME, LAST_NAME) VALUES(?,?)";
List parameters = Arrays.asList( "Foo", "Bar" );
int numRowsUpdated = update(connection, sqlUpdate, parameters);
connection.commit();
System.out.println("# rows inserted: " + numRowsUpdated);
System.out.println("after insert: " + query(connection, sqlQuery, Collections.EMPTY_LIST));
}
catch (Exception e)
{
rollback(connection);
e.printStackTrace();
}
finally
{
close(connection);
}
}
public static Connection createConnection(String driver, String url, String username, String password) throws ClassNotFoundException, SQLException
{
Class.forName(driver);
if ((username == null) || (password == null) || (username.trim().length() == 0) || (password.trim().length() == 0))
{
return DriverManager.getConnection(url);
}
else
{
return DriverManager.getConnection(url, username, password);
}
}
public static void close(Connection connection)
{
try
{
if (connection != null)
{
connection.close();
}
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static void close(Statement st)
{
try
{
if (st != null)
{
st.close();
}
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static void close(ResultSet rs)
{
try
{
if (rs != null)
{
rs.close();
}
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static void rollback(Connection connection)
{
try
{
if (connection != null)
{
connection.rollback();
}
}
catch (SQLException e)
{
e.printStackTrace();
}
}
public static List<Map<String, Object>> map(ResultSet rs) throws SQLException
{
List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
try
{
if (rs != null)
{
ResultSetMetaData meta = rs.getMetaData();
int numColumns = meta.getColumnCount();
while (rs.next())
{
Map<String, Object> row = new HashMap<String, Object>();
for (int i = 1; i <= numColumns; ++i)
{
String name = meta.getColumnName(i);
Object value = rs.getObject(i);
row.put(name, value);
}
results.add(row);
}
}
}
finally
{
close(rs);
}
return results;
}
public static List<Map<String, Object>> query(Connection connection, String sql, List<Object> parameters) throws SQLException
{
List<Map<String, Object>> results = null;
PreparedStatement ps = null;
ResultSet rs = null;
try
{
ps = connection.prepareStatement(sql);
int i = 0;
for (Object parameter : parameters)
{
ps.setObject(++i, parameter);
}
rs = ps.executeQuery();
results = map(rs);
}
finally
{
close(rs);
close(ps);
}
return results;
}
public static int update(Connection connection, String sql, List<Object> parameters) throws SQLException
{
int numRowsUpdated = 0;
PreparedStatement ps = null;
try
{
ps = connection.prepareStatement(sql);
int i = 0;
for (Object parameter : parameters)
{
ps.setObject(++i, parameter);
}
numRowsUpdated = ps.executeUpdate();
}
finally
{
close(ps);
}
return numRowsUpdated;
}
}
A statement inserting rows does not return any rows back as a result, as opposed to a SELECT.