Well I have been updating a legacy code since last few days.
Explanation:
There is a table CUSTOMER with columns blah1, blah2, blah3, blah4, blah.....
As per our architecture I need to create an insert statement dynamically which can insert data into any table with any number of column.
Currently we have following code.
public void save(Table table, Connection conn) throws Exception {
PreparedStatement pstmt = null;
try {
List<Row> rows = table.getRows();
String sql = "";
if(!rows.isEmpty() && rows != null)
{
for(Row row: rows) //READ EACH ROW
{
String columnName = ""; String columnValue = "";
List<String> params = new ArrayList<String>();
List<Column> columns = row.getColumns();
if(!columns.isEmpty() && columns != null)
{
for(Column column: columns) //GET EACH COLUMN DATA
{
columnName += ", "+column.getName();
columnValue += ", ?";
String value = column.getValue();
params.add(value); //ADD VALUE TO PARAMS
}
//INSERT QUERY
sql = "INSERT INTO "+table.getTableName()+" ("+columnName+") VALUES ("+columnValue+")";
if(pstmt == null) pstmt = conn.prepareStatement(sql);
//POPULATE PREPARED STATEMENT
for (int i =0; i<params.size(); i++) {
pstmt.setString(i+1, (String)params.get(i));
}
pstmt.addBatch();
}
}
pstmt.executeBatch();//BATCH COMMIT
conn.commit();
}
} catch (Exception e) {
if (conn != null) {
conn.rollback();
}
throw e;
}
}
Now instead of using the typical pstmt.executeBatch(). I want to use spring batch update as follows:
public void save(Table table, Connection conn) throws Exception{
String sql = createSaveQuery(table);//CREATES the INSERT Query
getJdbcTemplate().batchUpdate(sql.toString(), new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int j) throws SQLException {
//PROBLEM AREA: How to map this for each insert statement?
for(int i =0; i < params.size(); i++){
ps.setString(i+1, (String)params.get(i));
}
}
#Override
public int getBatchSize() {
return 0;
}
});
}
But I cannot figure out how to set the params for the each insert Query. As in the we can set the pstmt.setString(i, params.get(i)); for each row. How to achieve the same in 'new BatchPreparedStatementSetter()'.
any suggestions will be appreciated. If you need to further imporve the explanation. Please let me know.
I guess it's against Spring Batch nature to create dynamic SQL queries. That's because of PreparedStatement nature (read more here: What does it mean when I say Prepared statement is pre-compiled?). Long story short - PreparedStatements are compiled on DB side and they are really fast. If you modify SQL query, it can't be reused. It is hard to give advise without knowing of actual DB schema, but in general you should have one POJO for CUSTOMER table (I hope you don't have BLOBS) and you should read-write all fields. It will be faster than "optimized" dynamic query.
Related
Query doesn't work in IntelliJ but it does in SQL workbench. On other queries the updatePreparedStatement method works. I've checked my query for syntax errors and strangely enough it does run in sql workbench. Can somebody help a noob out?
public void updatePreparedStatement(String queryWithParameters, Object ... values) {
ArrayList<Map<String, Object>> result = new ArrayList<>();
try(PreparedStatement ps = this.connection.prepareStatement(queryWithParameters)) {
for(int i=0; i<values.length; i++) {
ps.setObject(i+1, values[i]);
}
int rs = ps.executeUpdate();
}catch (SQLException e){
e.printStackTrace();
}
}
public void assignOrdersToDeliveryRoute(BigInteger routeNumber, int[] orderNumbers){
DbConnection connection = new DbConnection();
System.out.println(Arrays.toString(orderNumbers));
StringBuilder orderNumberString = new StringBuilder();
int index =1;
for (int orderNumber : orderNumbers) {
String order = String.valueOf(orderNumber);
if (index < orderNumbers.length ){
orderNumberString.append(order).append(",");
}
else {
orderNumberString.append(order);
}
index++;
}
System.out.println(orderNumberString);
String query = "SET FOREIGN_KEY_CHECKS=0; UPDATE bestelling SET bezorgroutenummer = ? WHERE bestelnummer IN (" + orderNumberString + ")";
System.out.println(query);
connection.updatePreparedStatement(query, routeNumber);
}
https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html says:
SQL syntax for prepared statements does not support multi-statements (that is, multiple statements within a single string separated by ; characters).
Run your statements one at a time.
I have created a custom function to insert data in my MySQL database. The functions first creates a query based on the input given. The query wil look like INSERT INTO tableName (columnName1, ..., columnNamei) VALUES (?, ..., ?), ..., (?, ...,?). After that, the PreparedStatement needs to made, which contains the real values. These need to be added to a batch, because I want to add multiple rows at once (as showed here: Java: Insert multiple rows into MySQL with PreparedStatement). Here is the code:
insertData() Function
public static void insertData(String table, List<HashMap<String, Object>> list) throws SQLException {
//Create query: make sure all of the rows in the table get the same amount of values passed
//Prepare colnames string
String colNamesParsed = "";
int counter = 1;
//Iterate over only the first hashmap of the list (THATS WHY ALL THE ROWS NEED TO HAVE THE SAME AMOUNT OF VALUES PASSED)
for (String colName : list.get(0).keySet()) {
//Check if it is the last col name
if (counter != list.get(0).keySet().size()) {
colNamesParsed = colNamesParsed + colName+", ";
}
else {
colNamesParsed = colNamesParsed + colName;
}
counter++;
}
//Now create the place holder for the query variables
String queryVariablesPlaceholder = "";
int rowSize = 0;
for (HashMap<String, Object> row : list) {
//This part is to check if all row sizes are equal
if (rowSize == 0) {
rowSize = row.values().size();
}
else {
//Check if the rowsize is equal for all rows
if (row.values().size() != rowSize) {
System.out.println("The rows of the arrays are from a different size");
return;
}
}
String queryVariablesRow = "(?, ";
for (int j = 1; j < (row.values().size()-1); j++) {
queryVariablesRow = queryVariablesRow+"?, ";
}
queryVariablesRow = queryVariablesRow+"?)";
//Make sure the query does not start with a comma
if (queryVariablesPlaceholder.equals("")) {
queryVariablesPlaceholder = queryVariablesRow;
}
else {
queryVariablesPlaceholder = queryVariablesPlaceholder+", "+queryVariablesRow;
}
}
//The MySQL query needs to be built now
String query = "INSERT INTO "+table+" ("+colNamesParsed+") VALUES "+queryVariablesPlaceholder+";";
System.out.println(query);
//Init prepared statement
PreparedStatement statement = con.prepareStatement(query);
for (HashMap<String, Object> map : list) {
int varCounter = 1;
//Iterate over all values that need to be inserted
for (Object object : map.values()) {
if (object instanceof Integer) {
statement.setInt(varCounter, Integer.parseInt(object.toString()));
}
else if (object instanceof String) {
statement.setString(varCounter, object.toString());
}
else if (object instanceof Timestamp) {
statement.setTimestamp(varCounter, parseStringToTimestamp(object.toString()));
}
else if (object instanceof Double) {
statement.setDouble(varCounter, Double.parseDouble(object.toString()));
}
System.out.println(varCounter);
varCounter++;
}
//Add row to the batch
try {
statement.addBatch();
}
catch (SQLException e) {
e.printStackTrace();
}
}
//Execute the query, which is in fact the batch
statement.executeBatch();
}
When I want to insert some data in the database, I execute the following code:
Functional part
List<HashMap<String, Object>> list = new ArrayList<>();
for (Object object : listOfObjects) {
HashMap<String, Object> map = new HashMap<>();
map.put("columnName1", object.getSomeValue());
/....../
map.put("columnName2", object.getSomeOtherValue());
list.add(map);
}
Functions.insertData("tableName", list);
Creating the dynamic query seems to work perfectly. However, I can't get the statement.addBatch() to work. It keeps giving me the following error:
java.sql.SQLException: No value specified for parameter 9
I don't get it, because I only have 8 parameters to pass in every unit of the batch. My target table has 9 columns, so I tried to add a value for that column, but then it says: No value specified for parameter 10, so it seems like it isn't closing the 'batch unit' or something.
What am I missing here?
Any help is greatly appreciated!
This
INSERT INTO tableName (columnName1, ..., columnNamei) VALUES (?, ..., ?), ..., (?, ...,?)
is not standard SQL syntax.
If you use this JDBC will a parameter for each "?" in your query.
Use:
INSERT INTO tableName (columnName1, ..., columnNamei) VALUES (?, ..., ?)
and add every statement to a batch.
I am currently working on a Java project (on NetBeans) and I am struggling with a problem.
In fact, I have a jTable which contains several elements, which element has a jCheckBox in the second column and I would like to make a query to add the selected element (selected by the jCheckBox of course) in a table.
I can get the data that I want to add, but my query works only once. I have already check my loop but I don't where the problem comes from.
I let you see the code :
try {
// Getting id of the selected value in the jComboBox
String idParcours = oParcoursDAO.findIdParcours(jComboBoxParcours.getSelectedItem().toString());
int id = Integer.parseInt(idParcours);
// for each value in the jTable
for(int i=0; i <jTable2.getRowCount(); i++){
boolean isChecked = (Boolean)jTable2.getValueAt(i, 1);
String nomPoi = (String)jTable2.getValueAt(i, 0);
// if the value is selected
if(isChecked){
String IDPoi = oParcoursDAO.findIdPoi(nomPoi);
int idpoi = Integer.parseInt(IDPoi);
System.out.println("idpoi "+idpoi); // It works I saw as idpoi as I have choose
System.out.println("id "+id) // It works too
oParcoursDAO.addPoi(idpoi,id); // it works only once
}
}
}catch (SQLException ex) {
Logger.getLogger(ModificationParcoursJInternalFrame.class.getName()).log(Level.SEVERE, null, ex);
}
Thank you in advance for your help.
This is my statement
public void addPoi(int idPoi,int idParcours) throws SQLException{
String query = "INSERT INTO TB_POI_PARCOURS (id_poi,id_parcours) VALUES (?,?) ";
PreparedStatement preparedStatement = conn.prepareStatement(query);
preparedStatement.setInt(1,idPoi);
preparedStatement.setInt(2,idParcours);
preparedStatement.executeUpdate();
preparedStatement.close();
}
Why are you running one query per line? You can execute all of them in a single SQL using batch queries. It will require you to change the code but it will make it more efficient:
public void addPoi(Map<integer,Integer> poiMap) throws SQLException{
String query = "INSERT INTO TB_POI_PARCOURS (id_poi,id_parcours) VALUES (?,?) ";
PreparedStatement preparedStatement = conn.prepareStatement(query);
for(Integer idPoi:poiMap.keySet()) {
preparedStatement.setInt(1,idPoi);
preparedStatement.setInt(2,poiMap.get(idPoi));
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
preparedStatement.close();
}
Of course the original method has to be changed accordingly.
I want to create Java method which can count the rows in Oracle table. So far I made this:
public int CheckDataDB(String DBtablename, String DBArgument) throws SQLException {
System.out.println("SessionHandle CheckUserDB:"+DBArgument);
int count;
String SQLStatement = null;
if (ds == null) {
throw new SQLException();
}
Connection conn = ds.getConnection();
if (conn == null) {
throw new SQLException();
}
PreparedStatement ps = null;
try {
conn.setAutoCommit(false);
boolean committed = false;
try {
SQLStatement = "SELECT count(*) FROM ? WHERE USERSTATUS = ?";
ps = conn.prepareStatement(SQLStatement);
ps.setString(1, DBtablename);
ps.setString(2, DBArgument);
ResultSet result = ps.executeQuery();
if (result.next()) {
count = result.getString("Passwd");
}
conn.commit();
committed = true;
} finally {
if (!committed) {
conn.rollback();
}
}
} finally {
/* Release the resources */
ps.close();
conn.close();
}
return count;
}
I want to use for different tables. This is the problem that I cannot solve:
count = result.getString("row");
Can you help me to solve the problem?
count = result.getInt(1);
This is needed, because count is int. And you can specify the index of the row returned by the query, you don't need to access it by name.
But you could also do:
count = result.getInt("count(*)");
This should do it:
count = result.getInt("count(*)");
You need to use the same name as you specified in your query to get the value. You could also make your
count = result.getString("row");
work by changing your query to
SQLStatement = "SELECT count(*) as row FROM ? WHERE USERSTATUS = ?";
You cannot use bind variable in place of a database object in an SQL query, can you? It can only be used for parameter binding.
Try this instead,
"SELECT count(*) as row_count FROM " + DBtablename + " WHERE USERSTATUS = ?";
This could be vulnerable to SQL Injection so you might want to check that DBtablename parameter is a valid database object name (i.e. at most 30 bytes long without spaces, and contains only valid chars for database object identifiers).
count = result.getInt("row_count");
I'm using Spring-orm and HibernateTemplate to execute a native SQL query (DB is Oracle 11 for the reference), like this:
#Override
public List<Object> executeNativeQuery(final String queryStr, final Map<String, String> params) {
List<Object> results = this.template.execute(new HibernateCallback<List<Object>>() {
#Override
public List<Object> doInHibernate(Session session) throws HibernateException, SQLException {
// Get the query
Query query = session.createSQLQuery(queryStr);
// Assign parameters to the query, if any
if (params != null) {
for (Map.Entry<String, String> entry : params.entrySet()) {
query.setString(entry.getKey(), entry.getValue());
}
}
// fire the query
#SuppressWarnings("unchecked")
List<Object> res = query.list();
return res;
}
});
return results;
}
I've managed to successfully execute the query and get the results back. But I couldn't figure out a way to also get the resulting column names, and I'm starting to think that's not possible using this approach.
My problem is that I have to execute a query that comes from user input and I have no clues about parameter names.
Any ideas?
I finally found a way through it, so I post it hoping it'll be useful for others. I was doing it the wrong way, the correct way (at least for what my needs are) is to use doWork.
instad of:
session.createSQLQuery(queryStr);
I had to get the connection like this:
session.doWork(new Work() {
#Override
public void execute(Connection con) throws SQLException {
try {
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(queryStr);
ResultSetMetaData md = rs.getMetaData();
int col = md.getColumnCount();
System.out.println("Number of Column : " + col);
System.out.println("Columns Name: ");
for (int i = 1; i <= col; i++) {
String col_name = md.getColumnName(i);
System.out.println(col_name);
}
} catch (SQLException s) {
System.out.println("SQL statement is not executed!");
}
}
});
Try the following to get column names in Hibernate:
public ArrayList<String> getTableDesc(String tableName){
System.out.println("getFieldNames:start"+tableName);
Object[] a;
List<Object[]> fieldNames = new ArrayList<Object[]>();
ArrayList<String> tabFieldNames = new ArrayList<String>();
Session session = getHibernateTemplate().getSessionFactory().openSession();
try{
String queryStr = "desc "+tableName;
fieldNames = (List<Object[]>) session.createSQLQuery(queryStr).list();
for(int i=0;i<fieldNames.size();i++){
a = fieldNames.get(i);
tabFieldNames.add(a[0].toString());
}
}
catch(Exception e){
System.out.println("exception "+e);
}
finally{
session.close();
}
System.out.println("getFieldNames:end"+tabFieldNames.toString());
return tabFieldNames;
}
You can use ResultTransformer class to map the query result to an entity class.
http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/querysql.html#d0e17313
EDIT 1:
for( int i= 0; i< ((Object[])res.get(0)).length ; i++){
//do something with data
res.get(0)[i]
}