I am trying to deal with multitheading in Java.
I have read many articles and question(here on StackOverflow) but didn't find any clear examples how to use it.
I have Unique_Numbers table in HsqlDB database. There are 2 columns: NUMBER and QTY.
My task is to check if number exsists and increase QTY of number if yes and insert this number if not.
So, what did I get.
This is my configuration of Database
private final ComboPooledDataSource dataSource;
public Database(String url, String userName, String password) throws PropertyVetoException {
dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("org.hsqldb.jdbcDriver");
dataSource.setJdbcUrl(url);
dataSource.setUser(userName);
dataSource.setPassword(password);
dataSource.setMaxPoolSize(10);
dataSource.setMaxStatements(180);
dataSource.setMinPoolSize(5);
dataSource.setAcquireIncrement(5);
}
This is my logic:
public void insertRow(String number) throws SQLException {
int cnt = getCount(number);
if (cnt == 0) {
insert(number);
} else if (cnt > 0) {
update(number);
}
}
Get count of number in the table
private int getCount(String number) {
int cnt = 0;
String sql = "select count(number) as cnt from \"PUBLIC\".UNIQUE_NUMBER where number='" + number + "'";
try {
Statement sta;
try (Connection connection = dataSource.getConnection()) {
sta = connection.createStatement();
ResultSet rs = sta.executeQuery(sql);
if (rs.next()) {
cnt = rs.getInt("cnt");
}
}
sta.close();
} catch (Exception e) {
LOGGER.error("error select cnt by number" + e.toString());
}
return cnt;
}
Insert and update
private boolean insert(String number) throws SQLException {
String sql = "insert into \"PUBLIC\".UNIQUE_NUMBER (number, qty) values(?, ?)";
try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, number);
ps.setInt(2, 0);
ps.addBatch();
ps.executeBatch();
try {
connection.commit();
} catch (Exception e) {
connection.rollback();
LOGGER.error(e.toString());
return false;
}
}
}
return true;
}
private boolean update(String number) throws SQLException {
String sql = "update \"PUBLIC\".UNIQUE_NUMBER set (qty) = (?) where number = ?";
int qty = selectQtyByNumber(number) + 1;
try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setInt(1, qty);
ps.setString(2, number);
ps.executeUpdate();
try {
connection.commit();
} catch (Exception e) {
connection.rollback();
LOGGER.error(e.toString());
return false;
}
}
}
return true;
}
As I read, I must use Pool Connection. It is important to give one connection to each thread.
When I start my application, I got constraint exception or exception with Rollback: serialization failed.
What am I doing wrong?
Here is my logs
[INFO] [generate38] ERROR se.homework.hwbs.tasks.un.server.threads.InsertRowThread - exception while inserting numberintegrity constraint violation: check constraint; SYS_CT_10114 table: UNIQUE_NUMBER
[INFO] [generate38] ERROR se.homework.hwbs.tasks.un.server.database.Database - error select cnt by number java.sql.SQLTransactionRollbackException: transaction rollback: serialization failure
[INFO] [generate38] ERROR se.homework.hwbs.tasks.un.server.threads.InsertRowThread - exception while inserting numbertransaction rollback: serialization failure
[INFO] [generate38] ERROR se.homework.hwbs.tasks.un.server.database.Database - error select cnt by number java.sql.SQLTransactionRollbackException: transactionrollback: serialization failure
the non-transactional way
Do the increment first
update UNIQUE_NUMBER set qty = qty + 1 where number = ?
Check if it did update any row, insert the number if it didn't
int rowsMatched = ps.executeUpdate();
if(rowsMatched == 0) {
try {
insert into UNIQUE_NUMBER (number, qty) values(?, 0)
} catch(Exception e) {
// the insert will fail if another thread has already
// inserted the same number. check if that's the case
// and if so, increment instead.
if(isCauseUniqueConstraint(e)) {
update UNIQUE_NUMBER set qty = qty + 1 where number = ?
} else {throw e;}
}
}
No transaction handling (setAutoCommit(false), commit() or rollback()) reqired.
the transactional way
If you still want to do this in a transactional way, you need to do all steps within a single transaction, like #EJP suggested:
connection.setAutoCommit(false);
// check if number exists
// increment if it does
// insert if it doesn't
// commit, rollback & repeat in case of error
connection.setAutoCommit(true);
Set auto commit back to true if this code shares the connection pool with other code (as that's the default state others will expect the connection to be in) or make it clear that connections in the pool will always be in transactional mode.
In your code, getCount will sometimes get a connection in auto commit mode (first use) and sometimes get a connection in transactional mode (reused after insert and/or update) - that's why you see rollback exceptions in getCount.
Related
my system is having a database either mySQL or Oracle. I am writing a function to:
Check data in the database if id exists,
if data exists then do an UPDATE otherwise do an INSERT
My question is, is it efficient if I use UPSERT statement? Though i may still have 2 SQL string one for mySQL and one for Oracle.
Here is my current code so far:
public void insertOrUpdate(Report report) throws DBException {
String id = getId(report.id);
String update =
"UPDATE delivery SET url=?, response=?, created_date=? " +
"WHERE id=?";
String insert = "INSERT INTO delivery (id, external_id, desc, url, response," +
" created_date) VALUES (?,?,?,?,?,?)";
PreparedStatement ps = null;
Connection con = null;
try {
con = getConnection();
if (StringUtils.isEmpty(id)) {
ps = con.prepareStatement(insert);
int idx = 1;
ps.setLong(idx++, report.id);
ps.setString(idx++, report.externalId);
ps.setLong(idx++, report.description);
ps.setString(idx++, report.url);
ps.setString(idx++, report.response);
ps.setString(idx++, report.createdDate);
ps.executeUpdate();
} else {
ps = con.prepareStatement(update);
int idx = 1;
ps.setString(idx++, report.url);
ps.setString(idx++, report.response);
ps.setString(idx++, report.createdDate);
ps.setLong(idx++, report.id);
ps.executeUpdate();
}
} catch (Exception e) {
LOG.error("Error while Inserting/Updating delivery ", e);
throw new DBException(e.getMessage(), e);
} finally {
close(ps);
returnConnection(con);
}
}
I am attempting to write a method that selects 2 entries into an employee database and removes them (Based on a salary field), I am currently using a counter to accomplish this, however I tried using setMaxRows() so my result set would only have two entries, thus eliminating the need for the counter. I am using try-with-resources to create my statement and that seems to be causing an issue.
public void downSize(Connection con) {
String sql = "SELECT * FROM " + schemaName + "."+tableName+" WHERE EMPLOYEE_SALARY>200000";
try (
PreparedStatement statement = con.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = statement.executeQuery();
)
{
int counter = 0;
System.out.println("Now pruning workforce...");
while(rs.next() && counter<2) {
String name = rs.getString("EMPLOYEE_NAME");
rs.deleteRow();
counter++;
System.out.println(name+" was laid off.");
}
} catch(Exception e) {
e.printStackTrace();
System.out.print("Sql exception happened");
}
}
I've got a following problem: I'm trying to insert data (in this case a username) into a table using the following code:
void AddNewUser(String name, Connection conn){
if(ret == null){
ret = new DB_Retriever(conn);
}
if(!ret.UserExists(name, conn)){
try{
Statement stm = conn.createStatement();
stm.executeUpdate(DB_OperationalData.insert_new_user[0][0] + name + DB_OperationalData.insert_new_user[0][1]);
stm.executeUpdate(DB_OperationalData.insert_new_user[1][0] + name + DB_OperationalData.insert_new_user[1][1]);
stm.close();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
By the way: It absolutely doesn't matter what I put in the catch clause, nothing that I put there is executed. Just to make everything clear, here is the content of the DB_OperationalData.insert_new_user String array:
public static final String[][] insert_new_user = {
{"INSERT INTO User (Username, Status) VALUES ('","','IN');"},
{"INSERT INTO Statistics (Player_ID) SELECT ID FROM User WHERE Username='","';"}};
The second statement is supposed to copy the ID of the user that is inserted and put it into Player_ID field of the Statistics table (Table User's ID is an autonumbered field).
The exception I get is:
Error while processing the query: java.sql.SQLException: ResultSet closed
What is interesting, is that it works and the data is added correctly but I simply do not want any exceptions thrown.
That's the console output I get:
This is 'data' Package Testing class
Connection to the database established.
The number of tables existing in the database is: 0
All the queries have been processed successfully
Adding new users:
Error while processing the query: java.sql.SQLException: ResultSet closed
All the lines above the Exception are my own printouts, so I know what has actually happened.
[EDIT]
I have changed the code to use the PreparedStatement instead of ordinary Statement and the current try clause looks as follows:
PreparedStatement pstm = conn.prepareStatement(DB_OperationalData.insert_new_user[0]);
pstm.setString(1, name);
pstm.addBatch();
conn.setAutoCommit(false);
pstm.executeBatch();
conn.setAutoCommit(true);
pstm.close();
And the output is (still regardless of the contents of the catch clause):
This is 'data' Package Testing class
Connection to the database established.
The number of tables existing in the database is: 0
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at org.sqlite.PrepStmt.batch(PrepStmt.java:173)
at org.sqlite.PrepStmt.setString(PrepStmt.java:254)
at data.DB_Writer.AddNewUser(DB_Writer.java:28)
at data.DataHandler.AddNewUser(DataHandler.java:94)
at data.Tester.main(Tester.java:18)
All the queries have been processed successfully
Adding new users:
Error while processing the query: java.sql.SQLException: ResultSet closed
[EDIT 2]
With regards to the original version, when I remove the stm.close(); there is absolutely no difference and I still get the 'ResultSet closed' Exception.
[EDIT 3]
Here is the code of the method that is calling the above:
public void AddNewUser(String username)throws IllegalUsernameException{
if(username.length()==0 || username.length()>20){
throw new IllegalUsernameException();
}
writer.AddNewUser(username, conn);
}
The connection to the database is established by this class:
class DB_Connection {
public static Connection getConnection(){
Connection conn = null;
try{
Class.forName("org.sqlite.JDBC");
}
catch(ClassNotFoundException e){
log("Error while loading the database driver: " + e);
return null;
}
try{
conn = DriverManager.getConnection("jdbc:sqlite:database.db");
}
catch(SQLException e){
log("Unable to connect to the database: " + e);
return null;
}
return conn;
}
public static void log(String msg){
System.out.println(msg);
}
}
The DB_Retriever's method that is checking for the existing username:
boolean UserExists(String name, Connection conn){
String result = "";
try{
Statement stm = conn.createStatement();
ResultSet rs = stm.executeQuery(DB_OperationalData.user_exists[0] + name + DB_OperationalData.user_exists[1]);
result = rs.getString("Username");
}
catch(SQLException e){
System.out.println("Error while processing the query: " + e);
}
if(result.equals(name)){
return true;
}
return false;
}
The only location where Error while processing the query: java.sql.SQLException: ResultSet closed could be printed to the console is in UserExists(..), unless there is another method with a similar catch block. Indeed the ResultSet is not used correctly in UserExists, what may cause the error.
For a more complete description of how to work with JDBC look at this answer or the JDBC documentation. A possible alternative to the existing UserExists is:
boolean userExists(String name, Connection conn) {
PreparedStatement stmt = null;
try{
stmt = conn.prepareStatement("SELECT COUNT(Username) FROM User WHERE Username = ?");
stmt.setString(1, name);
ResultSet rs = stmt.executeQuery();
rs.next(); // set cursor to first row
int count = rs.getInt(1);
rs.close();
return count > 0;
} catch(SQLException e) {
// propagate error
throw new RuntimeException(e);
} finally {
// clean up resources
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ignore) {
log("error on sql clean up", ignore);
}
}
}
}
I want to rollback all records have been inserted to table when exception occurs.
but conInsert.rollback() doesn't work.
Maybe I miss some code?
Here is my code
try {
Class.forName("com.mysql.jdbc.Driver");
con = DriverManager.getConnection(connectionUrl);
//con.setAutoCommit(false);
Statement st = con.createStatement();
String querySelectOrderInTp = "SELECT order_in_tp_id, order_in_tp_qty, order_in_tp_price, order_in_tp_article_tc_id, order_in_tp_warehouse_tc_id, inv_stock_tp_id, inv_stock_tp_qty_available from order_in_tp LEFT JOIN inv_stock_tp on(order_in_tp_warehouse_tc_id=inv_stock_tp_whouse_current_id AND order_in_tp_article_tc_id=inv_stock_tp_article_tc_id AND order_in_tp_price=inv_stock_tp_price) where order_in_tp_pick_up_timestamp = 'A' AND order_in_tp_date = '2013-06-11' GROUP BY order_in_tp_id;";
ResultSet rs = st.executeQuery(querySelectOrderInTp);
String queryUpdateInvStockTp = "INSERT INTO inv_stock_tp (cre_tms,upd_tms,cre_usr,upd_usr,version,usr_act,inv_stock_tp_id,inv_stock_tp_key, inv_stock_tp_whouse_current_id,inv_stock_tp_article_tc_id,inv_stock_tp_qty_available,inv_stock_tp_qty_min,inv_stock_tp_price) VALUES (NOW(),NOW(),'demo2','demo2',1,'A',null,'AAAA',?,?,?,0,2000.0000)";
conInsert = DriverManager.getConnection(connectionUrl);
conInsert.setAutoCommit(false);
ps = conInsert.prepareStatement(queryUpdateInvStockTp);
String queryUpdateOrderInTp = "UPDATE order_in_tp set order_in_tp_pick_up_timestamp = ? WHERE order_in_tp_id = ?";
psUpdate = con.prepareStatement(queryUpdateOrderInTp);
while(rs.next()) {
Integer qty = rs.getInt(7) - rs.getInt(2);
ps.setString(1, rs.getString(5));
ps.setString(2, rs.getString(4));
ps.setString(3, rs.getString(2));
ps.execute();
psUpdate.setString(1, "A");
psUpdate.setString(2, rs.getString(1));
psUpdate.execute();
ps.clearParameters();
psUpdate.clearParameters();
}
conInsert.commit();
} catch (Exception e) {
e.printStackTrace();
if (conInsert != null) {
try {
System.err.print("Transaction is being rolled back");
conInsert.rollback();
} catch (SQLException excep) {
excep.printStackTrace();
}
}
}
I make an exception in last record but all record before it still have been inserted.
By default, MySQL runs with autocommit mode enabled. This means that as soon as you execute a statement that updates (modifies) a table, MySQL stores the update on disk to make it permanent. I dont know java, but make sure that You have the START TRANSACTION statement, and then COMMIT or ROLLBACK.
How do I insert a list of values into a column in a MySQL table.
Here is my project:
public void settingAmount(List<String>lst)throws Exception{
// Accessing driver from jar files
Class.forName("com.mysql.jdbc.Driver");
// here we create a variable for the connection called con
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/ozon","root","root");
// here we create our query
Statement stmt = (Statement) con.createStatement();
//performing insert operation :)
lst.toArray(ss);
for(String ins:ss){
double d=Double.parseDouble(ins);
String insert = "INSERT INTO storage_other(total) VALUES ("+ins+");";
//String insert = "INSERT INTO storage_other(amount) VALUES ("+ls+");";
// Here we are going to Execute the query
stmt.executeUpdate(insert);
System.out.print("Done Seccesfully :)");
}
}
What you want to do is use batches. Batches allow you to send a list of statements to be done at the same time.
Here is an example
connection.setAutoCommit(false);
PreparedStatement ps = connection.prepareStatement("INSERT INTO storage_other(total) VALUES (?)");
for (String ins:ss){
ps.setObject(1, d);
ps.addBatch();
}
ps.executeBatch();
connection.commit();
This will be significantly faster than individual inserts on any table with indexes.
This is a method I used in order to insert some data in an Oracle SQL database.
private boolean submit(Connection con, String query){
try {
PreparedStatement preStatement;
preStatement = con.prepareStatement(query);
preStatement.executeQuery();
preStatement.close();
return true;
}catch (Exception e) {
System.out.println("Exception cought, updating log.");
return false;
}
}
You can prepare your insert statement and call this function to perform the action. Call it using your connection object and the query. It shall return true on completion false in case something goes wrong. If you want to log any errors, use e.getMessage() to get the error message as a String in the exception block.
As mentioned in the comments, try to use the PreparedStatement object to avoid SQL Injection attacks and also try to trim any ' you might have in your data.
Here's how I'd recommend you do it. A few thoughts:
Give the Connection to the object. That method should do one thing: the INSERT.
Should be transactional.
Should clean up resources when done.
Tell users to provide a List of Doubles if that is what the amounts are. Don't parse; let clients do that.
Here is complete code:
public class Inserter {
private static final String INSERT_SQL = "INSERT INTO storage_other(total) VALUES(?))";
private Connection connection;
public Inserter(Connection connection) {
this.connection = connection;
}
public int settingAmount(List<Double> amounts)throws SQLException {
int numAmountsInserted = 0;
PreparedStatement ps = null;
this.connection.setAutoCommit(false);
try {
ps = this.connection.prepareStatement(INSERT_SQL);
for(Double amount : amounts) {
ps.setDouble(1, amount);
numAmountsInserted += ps.executeUpdate();
}
this.connection.commit();
} catch (SQLException e) {
DatabaseUtils.rollback(this.connection);
throw e;
} finally {
DatabaseUtils.close(ps);
this.connection.setAutoCommit(true);
}
return numAmountsInserted;
}
}