I have a table inbox_participants that has inbox_id as a foreign key. When the inbox is updated, participants may be added or deleted. I am trying to delete all the inbox participants for the given inbox_id and reinsert updated participants with the same participant_id. It runs normally on my local machine. But in the server, it gives a Duplicate Entry exception for inbox_participant_id that should have been deleted.
Update Inbox
logger.info("Update inbox. Inbox id : {}", id);
final String query = "UPDATE inbox SET subject=?, updated_at=?, type=? WHERE inbox_id=?";
Connection conn = null;
PreparedStatement pst = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
pst = dataSource.prepareStatement(query, conn);
logger.debug(debug, QUERY, query);
pst.setString(1, inbox.getSubject());
pst.setLong(2, TreeleafDate.timestamp());
pst.setInt(3, inbox.getTypeValue());
pst.setString(4, id);
if (pst.executeUpdate() != 1) {
rollback(conn);
return false;
}
if (!removeParticipants(conn, id, debug)) {
rollback(conn);
return false;
}
if (!updateParticipants(conn, id, accountIdParticipantMap, debug)) {
rollback(conn);
return false;
}
commit(conn);
return true;
} catch (SQLException | JDBCException e) {
rollback(conn);
logger.error(debug, "Error while updating inbox", e);
return false;
} finally {
close(pst);
close(conn);
}
Remove Participants
private boolean removeParticipants(final Connection conn,
final String id,
final TreeleafProto.Debug debug) {
logger.info(debug, "Deleting inbox participants. Inbox id : {}", id);
final String query = "DELETE FROM inbox_participant WHERE inbox_id=?";
try (PreparedStatement pst = dataSource.prepareStatement(query, conn)) {
logger.debug(debug, QUERY,
query);
pst.setString(1, id);
final var i = pst.executeUpdate();
logger.debug(debug, "Delete query rows updated : {}", i);
return i >= 0;
} catch (JDBCException | SQLException e) {
logger.error(debug, "Error while removing participants.", e);
return false;
}
}
Insert updated participants
private boolean updateParticipants(final Connection conn,
final String id,
final Map<String, InboxProto.InboxParticipant> participants,
final TreeleafProto.Debug debug) {
logger.info(debug, "Updating inbox participants");
final String query = "INSERT INTO inbox_participant (inbox_participant_id, inbox_id, account_id, `role`, created_at, updated_at, notification_type, `left`) VALUES(?, ?, ?, ?, ?, ?, ?, ?)";
try (PreparedStatement pst = dataSource.prepareStatement(query, conn)) {
logger.debug(debug, QUERY,
query);
for (Map.Entry<String, InboxProto.InboxParticipant> entry : participants.entrySet()) {
final var participant = entry.getValue();
pst.setString(1, getId(participant));
pst.setString(2, id);
pst.setString(3, entry.getKey());
pst.setInt(4, participant.getRoleValue());
pst.setLong(5, TreeleafDate.timestamp());
pst.setLong(6, TreeleafDate.timestamp());
pst.setInt(7, participant.getNotificationTypeValue());
pst.setInt(8, participant.getParticipantStatusValue());
pst.addBatch();
}
int[] ints = pst.executeBatch();
return ints.length == participants.size() &&
Arrays.stream(ints).allMatch(value -> value == 1);
} catch (JDBCException | SQLException e) {
logger.error(debug, "Error while updating participants.", e);
return false;
}
}
If I understand the general logic here, it looks like the code above is attempting to either add (INSERT) OR UPDATE by-way-of-first-deleting-and-then-inserting based on whether the participants are already there or not.
If this is the gist of what you're trying to do, you should look into using an UPSERT syntax. This is a way to push this type of "INSERT or UPDATE if-it-exists-already" logic into the DML which is generally going to be easier to write in code (less code) and easier to test in your SQL console outside of the code, too.
Here's an example reference guide that talks about Upserts.
https://blog.usejournal.com/update-insert-upsert-multiple-records-in-different-db-types-63aa44191884
So in your code, this strategy would allow you to remove "removeParticipants" as a method and then instead of INSERT_INBOX_PARTICIPANT being a straight INSERT, like INSERT INTO inbox_participant, you would have something like...
MERGE INTO INBOX_PARTICIPANT T USING
(VALUES
(?, ?, ?)
) S (INBOX_ID, PARTICIPANT_ID, SOME_DATA)
ON T.INBOX_ID = S.INBOX_ID and T.PARTICIPANT_ID= S.PARTICIPANT_ID
WHEN MATCHED THEN UPDATE SET SOME_DATA = S.SOME_DATA
WHEN NOT MATCHED THEN INSERT (INBOX_ID, PARTICIPANT_ID, SOME_DATA)
VALUES (S.INBOX_ID, S.PARTICIPANT_ID, S.SOME_DATA);
**NOTE: The exact syntax varies according to your underlying database! See page for details! **
This will likely solve your problem indirectly but also lead to easier to maintain code.
As to if you want to continue with your original problem, I would investigate whether your DELETE's are somehow getting rolled back inadvertently.
Or perhaps because you INSERT in batch mode but delete as a direct executeUpdate, there's a possibility that the "batch" mode INSERTs are getting a different transactional context than the DELETEs, since "batch mode" may be attempting to start a separate transaction to manage the batch. To test this theory, maybe try making it so the removes and adds run in the same batch context.
Also if removeParticipants has NO existing participants to delete, it looks like you call rollback because i==0. Is this what you wanted?
Related
I have to access the automatically generated data (id, created, last_modified ...) when inserting data lists. Because the lists may be large, I use statement.executeBatch() to add everything in a package. However, this way I lose the opportunity to take advantage of the returning statement.
I am currently doing the following to get the data:
public boolean store(Connection connection, List<WorkPlace> list) throws SQLException {
String query =
"insert into work_places (merchant_id, name, description) values (?, ?, ?)";
try(PreparedStatement statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
for(WorkPlace workPlace: list) {
statement.setLong(1, workPlace.getMerchantId());
statement.setString(2, workPlace.getName());
statement.setString(3, workPlace.getDescription());
statement.addBatch();
}
statement.executeBatch();
try(ResultSet rs = statement.getGeneratedKeys()) {
List<Long> ids = new ArrayList<>();
while (rs.next()) {
ids.add(rs.getLong(1));
}
query =
"select * from work_places where id = any (?)";
try(PreparedStatement statement1 = connection.prepareStatement(query)) {
statement1.setArray(1, connection.createArrayOf("integer", ids.toArray()));
try(ResultSet rs1 = statement1.executeQuery()) {
list.clear();
while (rs1.next()) {
list.add(getWorkPlace(rs1));
}
}
}
}
}
return true;
}
Are you interested, is there a better way to achieve what I need?
The generated keys implementation in the PostgreSQL JDBC driver uses RETURNING *, which will return all columns from the table. So, if you can retrieve the generated id this way after executing a batch, then you should also be able to retrieve the other columns from the same getGeneratedKeys result set.
I have this java method:
public boolean insertAuthor(String userid, String password){
try{
String query1 = "INSERT INTO user (id, firstName, lastName, belonging, country) VALUES(?,?,?,?,?)";
PreparedStatement stmt = this.dbConn.prepareStatement(query1);
stmt.setInt(1,0);
stmt.setString(2,"default"); //Yes, it's correct with "default"
stmt.setString(3,"default");
stmt.setString(4,"default");
stmt.setString(5,"default");
//stmt.executeUpdate();
stmt.executeUpdate(query1, PreparedStatement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
int key=0;
if ( rs.next() ) {
key = rs.getInt(1);
}
String query2 = "INSERT INTO authentication (id, address, password, user_id, login_id) VALUES(?,?,?,?,?)";
stmt = this.dbConn.prepareStatement(query2);
stmt.setInt(1,0);
stmt.setString(2,"default");
stmt.setString(2,password);
stmt.setInt(2,key);
stmt.setString(2,userid);
stmt.executeUpdate();
return true;
}catch(Exception e){
System.err.println(e.getMessage());
}
return false;
}
Let me explain: I would like to execute two queries and the second one need the key that is generated in the first query (I need the primary key of the table "user" because user-authentication is a 1:1 relationship).
So:
Is this the correct way to execute more than one query?
Am I missing something with the returning key? Because if I run ONLY executeUpdate() and I comment every row below it the method works fine, but when I run the code in the example (with the first executeUpdate() commented) I get false (only false, no exception). Do I have to check something in my database?
Thanks in advance.
EDIT:
I found a solution. It was an error in columns and not in the method for getting the generated key itself. I will choose Joop Eggen's answer for the improvements that he showed me. Thanks!
There were a couple of improvements needed.
String query1 = "INSERT INTO user (firstName, lastName, belonging, country)"
+ " VALUES(?,?,?,?)";
String query2 = "INSERT INTO authentication (address, password, user_id, login_id)"
+ " VALUES(?,?,?,?)";
try (PreparedStatement stmt1 = this.dbConn.prepareStatement(query1,
PreparedStatement.RETURN_GENERATED_KEYS);
stmt2 = this.dbConn.prepareStatement(query2)) {
stmt1.setString(1, "default");
stmt1.setString(2, "default");
stmt1.setString(3, "default");
stmt1.setString(4, "default");
stmt1.executeUpdate();
try (ResultSet rs = stmt1.getGeneratedKeys()) {
if (rs.next()) {
int userid = rs.getInt(1);
stmt2.setString(1, "default");
stmt2.setString(2, password);
stmt2.setInt(3, key);
stmt2.setString(4, userid);
stmt2.executeUpdate();
return true;
}
}
} catch (SQLException e) {
System.err.println(e.getMessage());
}
return false;
Try-with-resources close automatically, also on exception and return.
You have two prepared statements to close.
The executeUpdate with the SQL is for the parents class Statement, and does disrespect the parameter settings. You chose that for the generated keys parameter, but that goes into Connection.prepareStatement.
(SQL) The generated keys should not be listed/quasi-inserted.
It is debatable whether one should catch the SQLException here. throws SQLException is what works for me.
I'll advise you have a username field in your user table so after inserting you can simply do a Select id from user Where username...
I want to INSERT a record in a database (which is Microsoft SQL Server in my case) using JDBC in Java. At the same time, I want to obtain the insert ID. How can I achieve this using JDBC API?
If it is an auto generated key, then you can use Statement#getGeneratedKeys() for this. You need to call it on the same Statement as the one being used for the INSERT. You first need to create the statement using Statement.RETURN_GENERATED_KEYS to notify the JDBC driver to return the keys.
Here's a basic example:
public void create(User user) throws SQLException {
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(SQL_INSERT,
Statement.RETURN_GENERATED_KEYS);
) {
statement.setString(1, user.getName());
statement.setString(2, user.getPassword());
statement.setString(3, user.getEmail());
// ...
int affectedRows = statement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating user failed, no rows affected.");
}
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
user.setId(generatedKeys.getLong(1));
}
else {
throw new SQLException("Creating user failed, no ID obtained.");
}
}
}
}
Note that you're dependent on the JDBC driver as to whether it works. Currently, most of the last versions will work, but if I am correct, Oracle JDBC driver is still somewhat troublesome with this. MySQL and DB2 already supported it for ages. PostgreSQL started to support it not long ago. I can't comment about MSSQL as I've never used it.
For Oracle, you can invoke a CallableStatement with a RETURNING clause or a SELECT CURRVAL(sequencename) (or whatever DB-specific syntax to do so) directly after the INSERT in the same transaction to obtain the last generated key. See also this answer.
Create Generated Column
String generatedColumns[] = { "ID" };
Pass this geneated Column to your statement
PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
Use ResultSet object to fetch the GeneratedKeys on Statement
ResultSet rs = stmtInsert.getGeneratedKeys();
if (rs.next()) {
long id = rs.getLong(1);
System.out.println("Inserted ID -" + id); // display inserted record
}
When encountering an 'Unsupported feature' error while using Statement.RETURN_GENERATED_KEYS, try this:
String[] returnId = { "BATCHID" };
String sql = "INSERT INTO BATCH (BATCHNAME) VALUES ('aaaaaaa')";
PreparedStatement statement = connection.prepareStatement(sql, returnId);
int affectedRows = statement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating user failed, no rows affected.");
}
try (ResultSet rs = statement.getGeneratedKeys()) {
if (rs.next()) {
System.out.println(rs.getInt(1));
}
rs.close();
}
Where BATCHID is the auto generated id.
I'm hitting Microsoft SQL Server 2008 R2 from a single-threaded JDBC-based application and pulling back the last ID without using the RETURN_GENERATED_KEYS property or any PreparedStatement. Looks something like this:
private int insertQueryReturnInt(String SQLQy) {
ResultSet generatedKeys = null;
int generatedKey = -1;
try {
Statement statement = conn.createStatement();
statement.execute(SQLQy);
} catch (Exception e) {
errorDescription = "Failed to insert SQL query: " + SQLQy + "( " + e.toString() + ")";
return -1;
}
try {
generatedKey = Integer.parseInt(readOneValue("SELECT ##IDENTITY"));
} catch (Exception e) {
errorDescription = "Failed to get ID of just-inserted SQL query: " + SQLQy + "( " + e.toString() + ")";
return -1;
}
return generatedKey;
}
This blog post nicely isolates three main SQL Server "last ID" options:
http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the-sql-server/ - haven't needed the other two yet.
Instead of a comment, I just want to answer post.
Interface java.sql.PreparedStatement
columnIndexes « You can use prepareStatement function that accepts columnIndexes and SQL statement.
Where columnIndexes allowed constant flags are Statement.RETURN_GENERATED_KEYS1 or Statement.NO_GENERATED_KEYS[2], SQL statement that may contain one or more '?' IN parameter placeholders.
SYNTAX «
Connection.prepareStatement(String sql, int autoGeneratedKeys)
Connection.prepareStatement(String sql, int[] columnIndexes)
Example:
PreparedStatement pstmt =
conn.prepareStatement( insertSQL, Statement.RETURN_GENERATED_KEYS );
columnNames « List out the columnNames like 'id', 'uniqueID', .... in the target table that contain the auto-generated keys that should be returned. The driver will ignore them if the SQL statement is not an INSERT statement.
SYNTAX «
Connection.prepareStatement(String sql, String[] columnNames)
Example:
String columnNames[] = new String[] { "id" };
PreparedStatement pstmt = conn.prepareStatement( insertSQL, columnNames );
Full Example:
public static void insertAutoIncrement_SQL(String UserName, String Language, String Message) {
String DB_URL = "jdbc:mysql://localhost:3306/test", DB_User = "root", DB_Password = "";
String insertSQL = "INSERT INTO `unicodeinfo`( `UserName`, `Language`, `Message`) VALUES (?,?,?)";
//"INSERT INTO `unicodeinfo`(`id`, `UserName`, `Language`, `Message`) VALUES (?,?,?,?)";
int primkey = 0 ;
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
Connection conn = DriverManager.getConnection(DB_URL, DB_User, DB_Password);
String columnNames[] = new String[] { "id" };
PreparedStatement pstmt = conn.prepareStatement( insertSQL, columnNames );
pstmt.setString(1, UserName );
pstmt.setString(2, Language );
pstmt.setString(3, Message );
if (pstmt.executeUpdate() > 0) {
// Retrieves any auto-generated keys created as a result of executing this Statement object
java.sql.ResultSet generatedKeys = pstmt.getGeneratedKeys();
if ( generatedKeys.next() ) {
primkey = generatedKeys.getInt(1);
}
}
System.out.println("Record updated with id = "+primkey);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
I'm using SQLServer 2008, but I have a development limitation: I cannot use a new driver for it, I have to use "com.microsoft.jdbc.sqlserver.SQLServerDriver" (I cannot use "com.microsoft.sqlserver.jdbc.SQLServerDriver").
That's why the solution conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) threw a java.lang.AbstractMethodError for me.
In this situation, a possible solution I found is the old one suggested by Microsoft:
How To Retrieve ##IDENTITY Value Using JDBC
import java.sql.*;
import java.io.*;
public class IdentitySample
{
public static void main(String args[])
{
try
{
String URL = "jdbc:microsoft:sqlserver://yourServer:1433;databasename=pubs";
String userName = "yourUser";
String password = "yourPassword";
System.out.println( "Trying to connect to: " + URL);
//Register JDBC Driver
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();
//Connect to SQL Server
Connection con = null;
con = DriverManager.getConnection(URL,userName,password);
System.out.println("Successfully connected to server");
//Create statement and Execute using either a stored procecure or batch statement
CallableStatement callstmt = null;
callstmt = con.prepareCall("INSERT INTO myIdentTable (col2) VALUES (?);SELECT ##IDENTITY");
callstmt.setString(1, "testInputBatch");
System.out.println("Batch statement successfully executed");
callstmt.execute();
int iUpdCount = callstmt.getUpdateCount();
boolean bMoreResults = true;
ResultSet rs = null;
int myIdentVal = -1; //to store the ##IDENTITY
//While there are still more results or update counts
//available, continue processing resultsets
while (bMoreResults || iUpdCount!=-1)
{
//NOTE: in order for output parameters to be available,
//all resultsets must be processed
rs = callstmt.getResultSet();
//if rs is not null, we know we can get the results from the SELECT ##IDENTITY
if (rs != null)
{
rs.next();
myIdentVal = rs.getInt(1);
}
//Do something with the results here (not shown)
//get the next resultset, if there is one
//this call also implicitly closes the previously obtained ResultSet
bMoreResults = callstmt.getMoreResults();
iUpdCount = callstmt.getUpdateCount();
}
System.out.println( "##IDENTITY is: " + myIdentVal);
//Close statement and connection
callstmt.close();
con.close();
}
catch (Exception ex)
{
ex.printStackTrace();
}
try
{
System.out.println("Press any key to quit...");
System.in.read();
}
catch (Exception e)
{
}
}
}
This solution worked for me!
I hope this helps!
You can use following java code to get new inserted id.
ps = con.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, quizid);
ps.setInt(2, userid);
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
lastInsertId = rs.getInt(1);
}
It is possible to use it with normal Statement's as well (not just PreparedStatement)
Statement statement = conn.createStatement();
int updateCount = statement.executeUpdate("insert into x...)", Statement.RETURN_GENERATED_KEYS);
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
return generatedKeys.getLong(1);
}
else {
throw new SQLException("Creating failed, no ID obtained.");
}
}
Most others have suggested to use JDBC API for this, but personally, I find it quite painful to do with most drivers. When in fact, you can just use a native T-SQL feature, the OUTPUT clause:
try (
Statement s = c.createStatement();
ResultSet rs = s.executeQuery(
"""
INSERT INTO t (a, b)
OUTPUT id
VALUES (1, 2)
"""
);
) {
while (rs.next())
System.out.println("ID = " + rs.getLong(1));
}
This is the simplest solution for SQL Server as well as a few other SQL dialects (e.g. Firebird, MariaDB, PostgreSQL, where you'd use RETURNING instead of OUTPUT).
I've blogged about this topic more in detail here.
With Hibernate's NativeQuery, you need to return a ResultList instead of a SingleResult, because Hibernate modifies a native query
INSERT INTO bla (a,b) VALUES (2,3) RETURNING id
like
INSERT INTO bla (a,b) VALUES (2,3) RETURNING id LIMIT 1
if you try to get a single result, which causes most databases (at least PostgreSQL) to throw a syntax error. Afterwards, you may fetch the resulting id from the list (which usually contains exactly one item).
In my case ->
ConnectionClass objConnectionClass=new ConnectionClass();
con=objConnectionClass.getDataBaseConnection();
pstmtGetAdd=con.prepareStatement(SQL_INSERT_ADDRESS_QUERY,Statement.RETURN_GENERATED_KEYS);
pstmtGetAdd.setString(1, objRegisterVO.getAddress());
pstmtGetAdd.setInt(2, Integer.parseInt(objRegisterVO.getCityId()));
int addId=pstmtGetAdd.executeUpdate();
if(addId>0)
{
ResultSet rsVal=pstmtGetAdd.getGeneratedKeys();
rsVal.next();
addId=rsVal.getInt(1);
}
If you are using Spring JDBC, you can use Spring's GeneratedKeyHolder class to get the inserted ID.
See this answer...
How to get inserted id using Spring Jdbctemplate.update(String sql, obj...args)
If you are using JDBC (tested with MySQL) and you just want the last inserted ID, there is an easy way to get it. The method I'm using is the following:
public static Integer insert(ConnectionImpl connection, String insertQuery){
Integer lastInsertId = -1;
try{
final PreparedStatement ps = connection.prepareStatement(insertQuery);
ps.executeUpdate(insertQuery);
final com.mysql.jdbc.PreparedStatement psFinal = (com.mysql.jdbc.PreparedStatement) ps;
lastInsertId = (int) psFinal.getLastInsertID();
connection.close();
} catch(SQLException ex){
System.err.println("Error: "+ex);
}
return lastInsertId;
}
Also, (and just in case) the method to get the ConnectionImpl is the following:
public static ConnectionImpl getConnectionImpl(){
ConnectionImpl conexion = null;
final String dbName = "database_name";
final String dbPort = "3306";
final String dbIPAddress = "127.0.0.1";
final String connectionPath = "jdbc:mysql://"+dbIPAddress+":"+dbPort+"/"+dbName+"?autoReconnect=true&useSSL=false";
final String dbUser = "database_user";
final String dbPassword = "database_password";
try{
conexion = (ConnectionImpl) DriverManager.getConnection(connectionPath, dbUser, dbPassword);
}catch(SQLException e){
System.err.println(e);
}
return conexion;
}
Remember to add the connector/J to the project referenced libraries.
In my case, the connector/J version is the 5.1.42. Maybe you will have to apply some changes to the connectionPath if you want to use a more modern version of the connector/J such as with the version 8.0.28.
In the file, remember to import the following resources:
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.mysql.jdbc.ConnectionImpl;
Hope this will be helpful.
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret = st.execute();
I want to INSERT a record in a database (which is Microsoft SQL Server in my case) using JDBC in Java. At the same time, I want to obtain the insert ID. How can I achieve this using JDBC API?
If it is an auto generated key, then you can use Statement#getGeneratedKeys() for this. You need to call it on the same Statement as the one being used for the INSERT. You first need to create the statement using Statement.RETURN_GENERATED_KEYS to notify the JDBC driver to return the keys.
Here's a basic example:
public void create(User user) throws SQLException {
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(SQL_INSERT,
Statement.RETURN_GENERATED_KEYS);
) {
statement.setString(1, user.getName());
statement.setString(2, user.getPassword());
statement.setString(3, user.getEmail());
// ...
int affectedRows = statement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating user failed, no rows affected.");
}
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
user.setId(generatedKeys.getLong(1));
}
else {
throw new SQLException("Creating user failed, no ID obtained.");
}
}
}
}
Note that you're dependent on the JDBC driver as to whether it works. Currently, most of the last versions will work, but if I am correct, Oracle JDBC driver is still somewhat troublesome with this. MySQL and DB2 already supported it for ages. PostgreSQL started to support it not long ago. I can't comment about MSSQL as I've never used it.
For Oracle, you can invoke a CallableStatement with a RETURNING clause or a SELECT CURRVAL(sequencename) (or whatever DB-specific syntax to do so) directly after the INSERT in the same transaction to obtain the last generated key. See also this answer.
Create Generated Column
String generatedColumns[] = { "ID" };
Pass this geneated Column to your statement
PreparedStatement stmtInsert = conn.prepareStatement(insertSQL, generatedColumns);
Use ResultSet object to fetch the GeneratedKeys on Statement
ResultSet rs = stmtInsert.getGeneratedKeys();
if (rs.next()) {
long id = rs.getLong(1);
System.out.println("Inserted ID -" + id); // display inserted record
}
When encountering an 'Unsupported feature' error while using Statement.RETURN_GENERATED_KEYS, try this:
String[] returnId = { "BATCHID" };
String sql = "INSERT INTO BATCH (BATCHNAME) VALUES ('aaaaaaa')";
PreparedStatement statement = connection.prepareStatement(sql, returnId);
int affectedRows = statement.executeUpdate();
if (affectedRows == 0) {
throw new SQLException("Creating user failed, no rows affected.");
}
try (ResultSet rs = statement.getGeneratedKeys()) {
if (rs.next()) {
System.out.println(rs.getInt(1));
}
rs.close();
}
Where BATCHID is the auto generated id.
I'm hitting Microsoft SQL Server 2008 R2 from a single-threaded JDBC-based application and pulling back the last ID without using the RETURN_GENERATED_KEYS property or any PreparedStatement. Looks something like this:
private int insertQueryReturnInt(String SQLQy) {
ResultSet generatedKeys = null;
int generatedKey = -1;
try {
Statement statement = conn.createStatement();
statement.execute(SQLQy);
} catch (Exception e) {
errorDescription = "Failed to insert SQL query: " + SQLQy + "( " + e.toString() + ")";
return -1;
}
try {
generatedKey = Integer.parseInt(readOneValue("SELECT ##IDENTITY"));
} catch (Exception e) {
errorDescription = "Failed to get ID of just-inserted SQL query: " + SQLQy + "( " + e.toString() + ")";
return -1;
}
return generatedKey;
}
This blog post nicely isolates three main SQL Server "last ID" options:
http://msjawahar.wordpress.com/2008/01/25/how-to-find-the-last-identity-value-inserted-in-the-sql-server/ - haven't needed the other two yet.
Instead of a comment, I just want to answer post.
Interface java.sql.PreparedStatement
columnIndexes « You can use prepareStatement function that accepts columnIndexes and SQL statement.
Where columnIndexes allowed constant flags are Statement.RETURN_GENERATED_KEYS1 or Statement.NO_GENERATED_KEYS[2], SQL statement that may contain one or more '?' IN parameter placeholders.
SYNTAX «
Connection.prepareStatement(String sql, int autoGeneratedKeys)
Connection.prepareStatement(String sql, int[] columnIndexes)
Example:
PreparedStatement pstmt =
conn.prepareStatement( insertSQL, Statement.RETURN_GENERATED_KEYS );
columnNames « List out the columnNames like 'id', 'uniqueID', .... in the target table that contain the auto-generated keys that should be returned. The driver will ignore them if the SQL statement is not an INSERT statement.
SYNTAX «
Connection.prepareStatement(String sql, String[] columnNames)
Example:
String columnNames[] = new String[] { "id" };
PreparedStatement pstmt = conn.prepareStatement( insertSQL, columnNames );
Full Example:
public static void insertAutoIncrement_SQL(String UserName, String Language, String Message) {
String DB_URL = "jdbc:mysql://localhost:3306/test", DB_User = "root", DB_Password = "";
String insertSQL = "INSERT INTO `unicodeinfo`( `UserName`, `Language`, `Message`) VALUES (?,?,?)";
//"INSERT INTO `unicodeinfo`(`id`, `UserName`, `Language`, `Message`) VALUES (?,?,?,?)";
int primkey = 0 ;
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
Connection conn = DriverManager.getConnection(DB_URL, DB_User, DB_Password);
String columnNames[] = new String[] { "id" };
PreparedStatement pstmt = conn.prepareStatement( insertSQL, columnNames );
pstmt.setString(1, UserName );
pstmt.setString(2, Language );
pstmt.setString(3, Message );
if (pstmt.executeUpdate() > 0) {
// Retrieves any auto-generated keys created as a result of executing this Statement object
java.sql.ResultSet generatedKeys = pstmt.getGeneratedKeys();
if ( generatedKeys.next() ) {
primkey = generatedKeys.getInt(1);
}
}
System.out.println("Record updated with id = "+primkey);
} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
I'm using SQLServer 2008, but I have a development limitation: I cannot use a new driver for it, I have to use "com.microsoft.jdbc.sqlserver.SQLServerDriver" (I cannot use "com.microsoft.sqlserver.jdbc.SQLServerDriver").
That's why the solution conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) threw a java.lang.AbstractMethodError for me.
In this situation, a possible solution I found is the old one suggested by Microsoft:
How To Retrieve ##IDENTITY Value Using JDBC
import java.sql.*;
import java.io.*;
public class IdentitySample
{
public static void main(String args[])
{
try
{
String URL = "jdbc:microsoft:sqlserver://yourServer:1433;databasename=pubs";
String userName = "yourUser";
String password = "yourPassword";
System.out.println( "Trying to connect to: " + URL);
//Register JDBC Driver
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();
//Connect to SQL Server
Connection con = null;
con = DriverManager.getConnection(URL,userName,password);
System.out.println("Successfully connected to server");
//Create statement and Execute using either a stored procecure or batch statement
CallableStatement callstmt = null;
callstmt = con.prepareCall("INSERT INTO myIdentTable (col2) VALUES (?);SELECT ##IDENTITY");
callstmt.setString(1, "testInputBatch");
System.out.println("Batch statement successfully executed");
callstmt.execute();
int iUpdCount = callstmt.getUpdateCount();
boolean bMoreResults = true;
ResultSet rs = null;
int myIdentVal = -1; //to store the ##IDENTITY
//While there are still more results or update counts
//available, continue processing resultsets
while (bMoreResults || iUpdCount!=-1)
{
//NOTE: in order for output parameters to be available,
//all resultsets must be processed
rs = callstmt.getResultSet();
//if rs is not null, we know we can get the results from the SELECT ##IDENTITY
if (rs != null)
{
rs.next();
myIdentVal = rs.getInt(1);
}
//Do something with the results here (not shown)
//get the next resultset, if there is one
//this call also implicitly closes the previously obtained ResultSet
bMoreResults = callstmt.getMoreResults();
iUpdCount = callstmt.getUpdateCount();
}
System.out.println( "##IDENTITY is: " + myIdentVal);
//Close statement and connection
callstmt.close();
con.close();
}
catch (Exception ex)
{
ex.printStackTrace();
}
try
{
System.out.println("Press any key to quit...");
System.in.read();
}
catch (Exception e)
{
}
}
}
This solution worked for me!
I hope this helps!
You can use following java code to get new inserted id.
ps = con.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, quizid);
ps.setInt(2, userid);
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
lastInsertId = rs.getInt(1);
}
It is possible to use it with normal Statement's as well (not just PreparedStatement)
Statement statement = conn.createStatement();
int updateCount = statement.executeUpdate("insert into x...)", Statement.RETURN_GENERATED_KEYS);
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
return generatedKeys.getLong(1);
}
else {
throw new SQLException("Creating failed, no ID obtained.");
}
}
Most others have suggested to use JDBC API for this, but personally, I find it quite painful to do with most drivers. When in fact, you can just use a native T-SQL feature, the OUTPUT clause:
try (
Statement s = c.createStatement();
ResultSet rs = s.executeQuery(
"""
INSERT INTO t (a, b)
OUTPUT id
VALUES (1, 2)
"""
);
) {
while (rs.next())
System.out.println("ID = " + rs.getLong(1));
}
This is the simplest solution for SQL Server as well as a few other SQL dialects (e.g. Firebird, MariaDB, PostgreSQL, where you'd use RETURNING instead of OUTPUT).
I've blogged about this topic more in detail here.
With Hibernate's NativeQuery, you need to return a ResultList instead of a SingleResult, because Hibernate modifies a native query
INSERT INTO bla (a,b) VALUES (2,3) RETURNING id
like
INSERT INTO bla (a,b) VALUES (2,3) RETURNING id LIMIT 1
if you try to get a single result, which causes most databases (at least PostgreSQL) to throw a syntax error. Afterwards, you may fetch the resulting id from the list (which usually contains exactly one item).
In my case ->
ConnectionClass objConnectionClass=new ConnectionClass();
con=objConnectionClass.getDataBaseConnection();
pstmtGetAdd=con.prepareStatement(SQL_INSERT_ADDRESS_QUERY,Statement.RETURN_GENERATED_KEYS);
pstmtGetAdd.setString(1, objRegisterVO.getAddress());
pstmtGetAdd.setInt(2, Integer.parseInt(objRegisterVO.getCityId()));
int addId=pstmtGetAdd.executeUpdate();
if(addId>0)
{
ResultSet rsVal=pstmtGetAdd.getGeneratedKeys();
rsVal.next();
addId=rsVal.getInt(1);
}
If you are using Spring JDBC, you can use Spring's GeneratedKeyHolder class to get the inserted ID.
See this answer...
How to get inserted id using Spring Jdbctemplate.update(String sql, obj...args)
If you are using JDBC (tested with MySQL) and you just want the last inserted ID, there is an easy way to get it. The method I'm using is the following:
public static Integer insert(ConnectionImpl connection, String insertQuery){
Integer lastInsertId = -1;
try{
final PreparedStatement ps = connection.prepareStatement(insertQuery);
ps.executeUpdate(insertQuery);
final com.mysql.jdbc.PreparedStatement psFinal = (com.mysql.jdbc.PreparedStatement) ps;
lastInsertId = (int) psFinal.getLastInsertID();
connection.close();
} catch(SQLException ex){
System.err.println("Error: "+ex);
}
return lastInsertId;
}
Also, (and just in case) the method to get the ConnectionImpl is the following:
public static ConnectionImpl getConnectionImpl(){
ConnectionImpl conexion = null;
final String dbName = "database_name";
final String dbPort = "3306";
final String dbIPAddress = "127.0.0.1";
final String connectionPath = "jdbc:mysql://"+dbIPAddress+":"+dbPort+"/"+dbName+"?autoReconnect=true&useSSL=false";
final String dbUser = "database_user";
final String dbPassword = "database_password";
try{
conexion = (ConnectionImpl) DriverManager.getConnection(connectionPath, dbUser, dbPassword);
}catch(SQLException e){
System.err.println(e);
}
return conexion;
}
Remember to add the connector/J to the project referenced libraries.
In my case, the connector/J version is the 5.1.42. Maybe you will have to apply some changes to the connectionPath if you want to use a more modern version of the connector/J such as with the version 8.0.28.
In the file, remember to import the following resources:
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.mysql.jdbc.ConnectionImpl;
Hope this will be helpful.
Connection cn = DriverManager.getConnection("Host","user","pass");
Statement st = cn.createStatement("Ur Requet Sql");
int ret = st.execute();
The below method is supposed to update MySql DB with the company info passed to it.
I have other methods that insert and delete and work fine, however this method runs without exceptions, and always returns 1.
The general_log file shows that it received the update string but there are still no changes.
The only time I can get it to work is if I run the code in the MySql workplace directly.
If you need more info to figure this out, please let me know.
I gave you all I thought was needed.
Thanks.
// SQL update string received from the program in the log file
// UPDATE couponsprojectdb.company SET Email = 'admin#MyCompany.org', Password = 'pass' WHERE ID = 3
public void updateCompanyById(Company c, long id) throws SQLException
{
Connection conn = pool.getConnection(); // Gets an available connection from pool
// Prepared statement string
String sql = ("UPDATE company SET Email = ?, Password = ? WHERE ID = ?");
PreparedStatement p = conn.prepareStatement(sql);
p.setString(1, c.getEmail());
p.setString(2, c.getPassword());
p.setLong(3, id);
int i = p.executeUpdate();
System.out.println("changes: " + i);
pool.releaseConnection(conn);
}
You never called conn.commit() after doing the executeUpdate(). The reason the Java code returns 1 is because it succeeded, but the database rolled back the UPDATE immediately after the transaction ended.
You also need to close your connections. Change your code to this:
try {
Connection conn = pool.getConnection();
String sql = ("UPDATE company SET Email = ?, Password = ? WHERE ID = ?");
PreparedStatement p = conn.prepareStatement(sql);
p.setString(1, c.getEmail());
p.setString(2, c.getPassword());
p.setLong(3, id);
int i = p.executeUpdate();
conn.commit(); // <-- MAKE SURE TO COMMIT THE TRANSACTION TO THE DATABASE!!!
System.out.println("changes: " + i);
pool.releaseConnection(conn);
} catch(Exception e) {
// handle errors here
} finally {
try { if (p != null) p.close(); } catch (Exception e) {};
try { if (conn != null) pool.releaseConnection(conn); } catch (Exception e) {};
}
Big hat tip to this SO post which got me thinking about your problem.