-1

I have a UI where there is HTML table with input fields as cells and some input field outside the HTML table, so there I am entering data and on click of save saving data into my data base using servlets

It is working fine when only one user is using it at a time but causing issue when two user clicks save at same time

What I am doing is:

  • After Clicking on save I am calling my servlet class where I have written a code to insert the data into DB
  • So in my servlet class firstly I am running a query to get max+1 no. of a column from db and then saving it and using that value further in my insert query

Issue I am facing is:

  • When two user clicks save on same time, the query which is to get max+1 runs and the next query which is to insert data runs but the second one throws error as java.sql.BatchUpdateException: Duplicate entry '2' for key 'PRIMARY' because that one is primary key column in db and it can't be duplicate

  • but why it is taking only 2 like only one no when two user has clicked save button it should take 2,3 I think Here I am not handling the multiple request in my servlet which I don't even know how to handle, I have googled a lot and read there that servlets it self manages the multiple processing at one time

  • So my issue is I want to insert data into my db when multiple user clicks save button at single time, if there are two user clicking save at same time it is causing issue

My code

int grnNo;
Connection con = null;
Statement statement = null;

protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

}

protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    String[] itemCode = request.getParameterValues("itemCodetd");
    String[] unitCode = request.getParameterValues("unittd");

    try {
        con = DBConnection.createConnection();
        statement = con.createStatement();

        String grnNoSql = "select max(GRNNUMBER)+1 as GRNNo from egoodsreceived";
        ResultSet resultSet1 = statement.executeQuery(grnNoSql);

        while (resultSet1.next()) {
            int grnNoLocal = resultSet1.getInt("GRNNo");

            if (grnNo != 0) {
                grnNo = grnNoLocal;
                System.out.println("in if  :" + grnNo);
            } else {
                grnNo = 1;
                System.out.println("in else  :" + grnNo);
            }

        }

        String query1 = "query to insert data";

        PreparedStatement ps = con.prepareStatement(query1);

        for (int i = 0; i < itemCode.length; i++) {
            if (itemCode[i] != "") {
                System.out.println("in for :" + grnNo);
                ps.setInt(1, grnNo);
                ps.setString(2, itemCode[i]);
                ps.setString(3, unitCode[i]);

                ps.addBatch();
            }
        }

        ps.executeBatch(); // here getting erro as duplicate primary key

    } catch (SQLException e) {
        System.out.println("SQL EXCPTION   91");
        e.printStackTrace();
    }

    doGet(request, response);
}
halfer
  • 19,824
  • 17
  • 99
  • 186
manish thakur
  • 760
  • 9
  • 35
  • 76

2 Answers2

1

The problem is that you are using instance variables in servlets. There is only a single instance of each servlet in the JVM, so concurrent requests will attempt to use the same variable at the same time.

Refactor grnNo, con and statement to local variables, then wrap the select and insert into a single transaction.

steven35
  • 3,747
  • 3
  • 34
  • 48
  • you are saying that I should move all the global variables to doPost(local)? – manish thakur Jul 29 '19 at 08:36
  • Move it to `protected void doPost(HttpServletRequest request, HttpServletResponse response)` – steven35 Jul 29 '19 at 08:38
  • You can make `grnNo` a local variable but there is still a race condition. Unless you want to synchronize the Java code then you need to delegate to the database for ID generation. – Alan Hay Jul 29 '19 at 08:50
  • A transaction is not going to help you here. You would need to synchronize the Java code. – Alan Hay Jul 29 '19 at 09:05
  • Just out of curiosity, why are you trying to increment and set the id manually? – steven35 Jul 29 '19 at 10:06
  • You can keep that in an instance variable. So define `int count;` at instance level, than in doPost() call `System.out.println(++count);` – steven35 Jul 29 '19 at 11:00
  • This solution is not thread safe. Multiple threads can read the same max value from the database betwen updates. – Alan Hay Oct 29 '19 at 19:00
1

You should define the column to use MySQL's auto increment functionality:

https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html

CREATE TABLE X(
     id MEDIUMINT NOT NULL AUTO_INCREMENT,
     PRIMARY KEY (id)
);

You need not then specify the column or its value in the insert statement: the value will then be automatically assigned by the database.

The other answer makes a valid point about thread safety with instance variables in Servlets however, without adding a synchronized block, making grnNo a local variable does not address the issue:

//Thread T1 executes the below and get the next val
//before T1 has written its changes, T2 executes the below and gets the next val
String grnNoSql = "select max(GRNNUMBER)+1 as GRNNo from egoodsreceived";

If you need the generated identifier in the application after the insert then you can use the getGeneratedKeys() method of java.sql.statement:

https://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#getGeneratedKeys()

Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • That is an alternate answer to it I have to do it this way – manish thakur Jul 29 '19 at 08:39
  • Sorry but have updated to show that without a synchronization block then making the variable local is not going to work. This is how every application does it and there is no good reason to deviate from that. – Alan Hay Jul 29 '19 at 09:04
  • hey there is no need to use `getGeneratedKeys()` That is just a no which not auto incriment, can you show me an example releated to my code – manish thakur Jul 29 '19 at 09:39
  • I have given you the answer to your problem. I'm not sure what else you need. – Alan Hay Jul 29 '19 at 09:41
  • 1. Alter your database column to be auto-increment and, 2. alter your SQL insert so it doesn't include the id field. That's it. – Alan Hay Jul 29 '19 at 09:45
  • but that I can't do I can't alter the DB tables, why the above answer is not working as I have putted all in my `doPost` made them local? – manish thakur Jul 29 '19 at 09:58
  • As I have clearly explained you can still have a race condition. You will need to synchronize around the database access to prevent this although that will obviously create a point of contention . http://tutorials.jenkov.com/java-concurrency/synchronized.html and https://stackoverflow.com/questions/19509543/how-we-can-make-servlet-synchronized – Alan Hay Jul 29 '19 at 10:12
  • in the above link it says synchronizing the servlets is not a good approach – manish thakur Jul 29 '19 at 10:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/197158/discussion-between-manish-thakur-and-alan-hay). – manish thakur Jul 29 '19 at 10:27
  • Which is why you let the database handle it. So you take my solution, or you synchronize the code. **These are the 2 options available to you to handle your issue.** Choose one. – Alan Hay Jul 29 '19 at 10:31