I have an web application that uses the AUTO INCREMENT value of one table to insert into other tables. I need to ensure that the value read for the Auto Increment column is correct in the presence of potential concurrent INSERTs into that table. Since each thread will have its own connection (from the container pool) do I still have to put the code within a transaction?
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "INSERT INTO KYC_RECORD ....";
int autoIncKeyFromApi = -1;
Connection connection = ....
try {
connection.setAutoCommit(false);
ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString( ... );
ps.executeUpdate();
rs = ps.getGeneratedKeys();
if (rs.next()) {
autoIncKeyFromApi = rs.getInt(1);
} else {
// throw an exception from here
}
connection.commit();
}
The value of autoincrement of the column is managed on database level. Therefore you can fetch the value to getGeneratedKeys() without worry in multithreaded environment.
The transaction is started as soon as you call the update SQL statement. It happens on database level. It stays open until you commit it manually or if autocommit is enabled.
If you need to get more info about transactions, see Java Tutorial.
Related
I'm getting the SQLNonTransientException error when trying to update one of my rows in a H2 database.
public static void setNewServiceInformationsToShown() {
try (Connection conn = DriverManager.getConnection("jdbc:h2:" + Main.config_db_location,
Main.config_db_username, Main.config_db_password)) {
//read data from database
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM BCSTASKS_SERVICE");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
if(rs.getString("Status").equals("Neu") && rs.getBoolean("wasShown") == false) {
rs.updateBoolean("WASSHOWN", true);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
The error message already suggests that I should use conn.createStatement and set the ResultSet to CONCUR_UPDATABLE. The error occurs at the line with rs.updateBoolean(...);
Error Message:
The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). [90140-210]
The problem is I don't know where and how I should use this method. In the same function or at the start of the program?
Most DB code I see doesn't attempt to use the fact that resultsets are updatable, and will instead fire off an additional UPDATE query, which works fine.
However, sure, H2 supports updateable resultsets too. However, some of the features that ResultSets have actually have quite a cost; the DB engine needs to do a boatload of additional bookkeeping to enable such features which have a performance cost. Lots of database queries are extremely performance sensitive, so by default you do not get the bookkeeping and therefore these features do not work. You need to enable them explicitly, that's what the error is telling you.
You're currently calling the 'wrong' preparedStatement method. You want the more extended one, where you pick and choose which additional bookkeeping you want H2 to do for you, in order to enable these things. You want this one.
conn.prepareStatement(
"SELECT * FROM BCSTASKS_SERVICE",
ResultSet.TYPE_SCROLL_INSENSITIVE, // [edited]
ResultSet.CONCUR_UPDATABLE);
That CONCUR_UPDATABLE thing is just a flag you pass to say: Please do the bookkeeping so that I can call .update.
[edited] This used to read 0 before, but as #MarkRotteveel pointed out, that's not valid according to the documentation.
You have to put update query for update data in database but you are going with select query that is the problem.
Select query is used if you have to fetch data from database.
Update query is used for update data in database where data already stored in database but you just overwrite data.
Here down is modified code:
public static void setNewServiceInformationsToShown() {
try (Connection conn = DriverManager.getConnection("jdbc:h2:" + Main.config_db_location,
Main.config_db_username, Main.config_db_password)) {
PreparedStatement stmt = conn.prepareStatement("UPDATE BCSTASKS_SERVICE SET wasShown = ? WHERE status = ? AND wasShown = ?");
stmt.setBoolean(1, true);
stmt.setString(2, "Neu");
stmt.setBoolean(3, false);
stmt.executeUpdate();
stmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
You need to create a separate query/prepareStatement for an update. In your case as far as I can see you need only one update query:
conn.prepareStatement("UPDATE BCSTASKS_SERVICE SET WASSHOWN=true where
Status = 'Neu' and wasShown = false "
I am using temporary tables inside my code in order to [some long sequnce of reasons here] in SQL Server, Java. I was executing my sql queries with using Stament object in java. However, recently I decided to use PreparedStatement in order to avoid injection thing.
My problem is when create a temporary table with using PreparedStatement, I can not reach it with the same prepared statement again. Here is a simple illustration:
sql = "select * into #someTable from (select someColumns from someOtherTable where smth = ? and smth2 = ?)"
PreparedStatement preparedStatement = conn.prepareStatement(sql);
for(int i=0; i<parameters.size(); i++){
preparedStatement.setString(i+1, parameters.get(i).toString());
}
this.rs = preparedStatement.executeQuery();
Until here, it is ok. After getting ResultSet and doing something with it, or without getting a resultSet just for preparedStatement.execute() does not makes difference, I can not reach the #someTable object again.
sql = "select count(*) from #someTable"
preparedStatement = conn.prepareStatement(sql);
this.rs = preparedStatement.executeQuery();
Here this.rs = preparedStatement.executeQuery(); part gives 'Invalid object name #someTable'. I am doing all of the things above with using one Connection object only and without closing or reopening it. I need to use that temp table again. Is there any way to create temp table with PreparedStatement object in java and reuse this temp table again and again? Regards,
Rather late to the party, but facing the same problem and finding the above answer wrong:
Read this article about the problem: https://learn.microsoft.com/en-us/sql/connect/jdbc/using-usefmtonly?view=sql-server-2017
I found that using a PreparedStatement to create the temp table wouldn't work, but if I changed to use a Statement to create the temp table it would work (even without the useFmtOnly).
So start with this (from the MS article) and build on it:
final String sql = "INSERT INTO #Bar VALUES (?)";
try (Connection c = DriverManager.getConnection(URL, USERNAME, PASSWORD)) {
try (Statement s = c.createStatement()) {
s.execute("CREATE TABLE #Bar(c1 int)");
}
try (PreparedStatement p1 = c.prepareStatement(sql); PreparedStatement p2 = c.prepareStatement(sql)) {
((SQLServerPreparedStatement) p1).setUseFmtOnly(true);
ParameterMetaData pmd1 = p1.getParameterMetaData();
System.out.println(pmd1.getParameterTypeName(1)); // prints int
ParameterMetaData pmd2 = p2.getParameterMetaData(); // throws exception, Invalid object name '#Bar'
}
}
The temp table you create in the first statement exists for the scope\lifetime of that request. As soon as you call another query, you're in a different scope so it is no longer present as it would have been cleaned up.
Solutions are either make 2 requests in the same call (not great) or create a global temp table that can be accessed by the second query (still not great).
The better solution is to create a stored procedure that does everything you need, with the temp table creation, querying and tidy up encapsulated in the procedure.
PS I can't see any surrounding code, but beware of SQL Injection when building queries in code like this.
Related info:
Scope of temporary tables in SQL Server
I am facing exception as
com.microsoft.sqlserver.jdbc.SQLServerException: Transaction (Process ID 493) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
When high number of users hit my site's particular transaction. This is because there is lock on a table and others requesting to acquire lock on this particular table.
Also 20 tables are used for this particular transaction and for at least five tables first delete query is executing then fresh data is inserted, which might be holding table for long and causing deadlock. Below is sample code.
public void save2(){
con = DBConnFactory.getConnection();
con.setAutoCommit(false);
String deleteQuery1 = "delete from TEST_TABLE1";
String insertQuery1 = "insert into TEST_TABLE1 values ('66','7')";
String deleteQuery2 = "delete from TEST_TABLE2";
String insertQuery2 = "insert into TEST_TABLE2 values ('66','7')";
String deleteQuery3 = "delete from TEST_TABLE3";
String insertQuery3 = "insert into TEST_TABLE3 values ('66','7')";
String deleteQuery4 = "delete from TEST_TABLE4";
String insertQuery4 = "insert into TEST_TABLE4 values ('66','7')";
ps1 = con.prepareStatement(deleteQuery1);
ps2 = con.prepareStatement(insertQuery1);
ps1.executeUpdate();
ps2.executeUpdate();
ps1 = con.prepareStatement(deleteQuery2);
ps2 = con.prepareStatement(insertQuery2);
ps1.executeUpdate();
ps2.executeUpdate();
ps1 = con.prepareStatement(deleteQuery3);
ps2 = con.prepareStatement(insertQuery3);
ps1.executeUpdate();
ps2.executeUpdate();
ps1 = con.prepareStatement(deleteQuery4);
ps2 = con.prepareStatement(insertQuery4);
ps1.executeUpdate();
ps2.executeUpdate();
System.out.println("success2");
con.commit();
}
public class DBConnection {
public static Connection getConnection() {
Connection conn = null;
try {
Context initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:/comp/env");
DataSource dataSource = (DataSource) envContext.lookup("jdbc/DBConnection");
if ((conn == null) || conn.isClosed()) {
conn = dataSource.getConnection();
}
} catch (NamingException e) {
LOG.error("DBConnFactory - JNDI namin error in getConnection =>"+ e.getMessage());
} catch (SQLException e) {
LOG.error("DBConnFactory - SQL error in getConnection =>"+ e.getMessage());
}
return conn;
}
}
I was thinking about deleting all the tables data in stored procedure and then inserting through JAVA based on my business logic. Would it help?
Please suggest how to resolve this, do I need to change my approach?
Since you are deleting all of the rows in the table, try using TRUNCATE TABLE TEST_TABLE1 instead. Truncate is much faster than a Delete, though there are additional restrictions on its use since it is a DDL statement.
String deleteQuery1 = "truncate table TEST_TABLE1;";
Another approach you can use is combine your Delete and Insert statement into a single batch:
String query1 = "delete from TEST_TABLE1; insert into TEST_TABLE1 values ('66','7');";
A Delete statement without a where clause takes a full table lock, which will prevent other delete or insert statements from executing. By combining the two statements into a single batch, you'll reduce the number of connections and
latency.
You can combine all of the delete and insert statements into a single batch.
One significant concern is your use of Delete without a where clause. Since you are running into Deadlocks, that means you have multiple connections hitting the same objects at the same time. For example, if connectionA is inserting into Test_Table1 while connectionB is trying to delete from the table, you run tremendous risk of a soft conflict in which the connection deletes the data in connectionA before it can be used for anything else. You really should not use Delete without a Where clause in a transaction system like this.
If you are trying to delete the specific value you're about to insert, you should add a Where clause to limit it. That may also change the locking level from Table to Row level, assuming you have proper indexing, and eliminate the deadlocks.
String query1 = "delete from TEST_TABLE1 where ID = '66'; insert into TEST_TABLE1 values ('66','7');";
Using Informix 12.10FC5DE and JDBC.4.10.JC5DE, I am trying to write a Java program that will perform a "controlled" delete on a table.
The database containing the table is logged and the table has "lock mode row".
The program will receive a maximum number of rows to delete and a perform periodic commits every X rows (to limit the number of locks and prevent long transactions).
Using SPL, I can declare a cursor with hold and do a foreach loop with "delete where current of".
Using the JDBC, I can use the resultSet methods .next() and .deleteRow() to perform what I want (I set up the connection and statement to not autocommit or close the resultSet on commit).
It works, but it is slow (under the hood the JDBC is sending something like "DELETE FROM TABLE WHERE COLUMN = ?").
The code I have is something similar to the following:
Class.forName("com.informix.jdbc.IfxDriver");
Connection conn = DriverManager.getConnection(url);
conn.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
conn.setAutoCommit(false);
String cmd = "SELECT id FROM teste_001 FOR UPDATE;";
Statement stmt = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE,
ResultSet.HOLD_CURSORS_OVER_COMMIT);
stmt.setFetchSize(100);
stmt.setCursorName("myowncursor");
ResultSet resultados = stmt.executeQuery(cmd); // Get the resulSet and cursor
int maximo = 2000;
int passo = 100;
int cTotal = 0;
int cIter = 0;
cmd2 = "DELETE FROM teste_001 WHERE CURRENT OF " +
resultados.getCursorName();
PreparedStatement stmtDel2 = conn.prepareStatement(cmd2);
while (resultados.next())
{
if (cIter < maximo)
{
int resultCode2 = stmtDel2.executeUpdate();
if (resultCode2 == 1)
{
cTotal++;
}
cIter++;
if ((cIter % passo) == 0)
{
conn.commit(); // Perform periodic commit
}
}
else
{
break; // maximum number of rows reached
}
}
conn.commit(); // Perform final commit
stmtDel2.close();
resultados.close();
stmt.close();
conn.close();
The problem is that when I perform the 1st periodic commit, I get this error when I try the next delete:
java.sql.SQLException: There is no current row for UPDATE/DELETE cursor.
SQLCODE = -266 ; MESSAGE: There is no current row for UPDATE/DELETE cursor. ; SQLSTATE = 24000
Seems the cursor is being closed even with me setting the "HOLD_CURSORS_OVER_COMMIT".
Does anyone know if what I am trying with the JDBC is possible?
EDIT:
Since informix suports de IBM DRDA protocol i configured a drda listener on my test informix instance and used DB2 UDB JDBC Universal Driver.
The code is pretty much the same, only the driver changes:
Class.forName("com.ibm.db2.jcc.DB2Driver");
With the DRDA driver, the cursor is kept open over commits and the program behaves as expected. Tracing the session on informix instance i get this type of statement:
DELETE FROM test_001 WHERE CURRENT OF SQL_CURSH600C1
So, informix does suport "HOLD_CURSORS_OVER_COMMIT" with the DRDA driver but i still cannot make it work with the "ifxDriver".
I'm new to using Oracle so I'm going off what has already been previously answered in this SO question. I just can't seem to get it to work. Here's the statement that I'm using:
declare
lastId number;
begin
INSERT INTO "DB_OWNER"."FOO"
(ID, DEPARTMENT, BUSINESS)
VALUES (FOO_ID_SEQ.NEXTVAL, 'Database Management', 'Oracle')
RETURNING ID INTO lastId;
end;
When I call executeQuery the PreparedStatement that I have made, it inserts everything into the database just fine. However, I cannot seem to figure out how to retrieve the ID. The returned ResultSet object will not work for me. Calling
if(resultSet.next()) ...
yields a nasty SQLException that reads:
Cannot perform fetch on a PLSQL statement: next
How do I get that lastId? Obviously I'm doing it wrong.
make it a function that returns it to you (instead of a procedure). Or, have a procedure with an OUT parameter.
Not sure if this will work, since I've purged all of my computers of anything Oracle, but...
Change your declare to:
declare
lastId OUT number;
Switch your statement from a PreparedStatement to a CallableStatement by using prepareCall() on your connection. Then register the output parameter before your call, and read it after the update:
cstmt.registerOutParameter(1, java.sql.Types.NUMERIC);
cstmt.executeUpdate();
int x = cstmt.getInt(1);
I tried with Oracle driver v11.2.0.3.0 (since there are some bugs in 10.x and 11.1.x, see other blog). Following code works fine:
final String sql = "insert into TABLE(SOME_COL, OTHER_COL) values (?, ?)";
PreparedStatement ps = con.prepareStatement(sql, new String[] {"ID"});
ps.setLong(1, 264);
ps.setLong(2, 1);
int executeUpdate = ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next() ) {
// The generated id
long id = rs.getLong(1);
System.out.println("executeUpdate: " + executeUpdate + ", id: " + id);
}
When you prepare the statement set the second parameter to RETURN_GENERATED_KEYS. Then you should be able to get a ResultSet off the statement object.
You can use Statement.getGeneratedKeys() to do this. You just need to make sure to tell JDBC what columns you want back using one of the method overloads for that, such as the Connection.prepareStatement overload here:
Connection conn = ...
PreparedStatement pS = conn.prepareStatement(sql, new String[]{"id"});
pS.executeUpdate();
ResultSet rS = pS.getGeneratedKeys();
if (rS.next()) {
long id = rS.getLong("id");
...
}
You don't need to do the RETURNING x INTO stuff with this, just use the basic SQL statement you want.
Are you doing that in a stored procedure ? According to this Oracle document, it won't work with the server-side driver.
The Oracle server-side internal driver does not support
the retrieval of auto-generated keys feature.