Cannot get auto generated ID from MSSQL with JDBC - java

public String getKey() {
Connection con = null;
Statement stmt = null;
String generatedKey = null;
try {
con = dataSrc.getConnection();
stmt = con.createStatement();
// ask to return generated key
int affectedRows = stmt.executeUpdate("Insert into CountTab(t) value('0')",
Statement.RETURN_GENERATED_KEYS);
if (affectedRows == 0) {
throw new SQLException(
"Creating row failed, no rows affected.");
}
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
ResultSetMetaData rsmd = generatedKeys.getMetaData();
int columnCount = rsmd.getColumnCount();
if (Log4j.log.isEnabledFor(Level.INFO)) {
Log4j.log.info("count: " + columnCount);
}
if (generatedKeys.next()) {
generatedKey = generatedKeys.getString(1);
if (Log4j.log.isEnabledFor(Level.INFO)) {
Log4j.log.info("key: " + generatedKey);
}
} else {
throw new SQLException(
"Creating row failed, no ID obtained.");
}
}
} catch (SQLException se) {
// Handle any SQL errors
throw new RuntimeException("A database error occured. "
+ se.getMessage());
} finally {
// Clean up JDBC resources
if (stmt != null) {
try {
stmt.close();
} catch (SQLException se) {
se.printStackTrace(System.err);
}
}
if (con != null) {
try {
con.close();
} catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
return generatedKey;
}
CountTab{---this is my table(designed by other)
id char(10) NOT NULL, ----Auto increament from 0000000001
t char(1) NOT NULL, ----just for insert to get the id
PRIMARY KEY(id)
};
I'm trying to use java 1.7.0 to get unique ID that is generated from MS SQL DB, and the code above is what I use to do this; I also got the sqljdbc_4.1.5605.100 from MSDN and added the jar to the classpath for my program.
My problem: The value I got from getKey() is NULL. I'm not sure why it happens because this function do added new row into my table at each run, but it didn't reply me the Key value but NULL(even in Log; But I do got 1 from columnCount).
I dig around the Stackoverflow and I didn't see any answer which is similar or fits my condition. Please help me if there are any solution.
==============
Update:
This is table schema
CREATE TABLE [CountTab](
[id] [char](10) NOT NULL,
[t] [char](1) NOT NULL,
CONSTRAINT [PK_CountTab] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
I think I might figured out why he/she made such table: Because the ID need to be started with date form like this yyMMddxxxx.
For example: If I have 3 insertions today, then I can see 3 record in table(1609060000, 1609060001, 1609060002); next day, new records will have its new beginning date(1609070000, 1609070001, 1609070002).
That is tricky, but might be useful for some area(but still I can't get the Key from RETURN_GENERATED_KEYS).

Nevermind, I created another table with auto increment identity and RETURN_GENERATED_KEYS works now. Look like it does not support the Key which is not identity.

Related

Cannot add or update a child row: a foreign key constraint fails when retrieving the maximum value of a field in MySQL database using Eclipse

I am trying to retrieve the maximum value of the Experiment ID field from the Experiments table, adapted from this code; however, there appears to be some syntax errors in my code, more specifically at the SELECT MAX - AS - FROM line:
public int returnExperimentNo() {
int maxID = 0;
Connection con = con();
try {
String sql = "SELECT MAX(Experiment ID) AS max_id FROM Experiments";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
if(rs.next()) {
maxID = rs.getInt("max_id");
}
} catch (Exception e) {
System.out.println("Error: " + e);
}
return maxID;
}
I have tried putting Experiment ID in quotation mark (ā€œ), apostrophe (ā€˜) and backtick (`); however, none of these worked. This is the error I got when Experiment ID is wrapped in backticks (the database is relational and Experiment ID is a foreign key used in the Logs table):
Error: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (stockcontrolsystem.logs, CONSTRAINT logs_ibfk_2 FOREIGN KEY (Experiment ID) REFERENCES Experiments (Experiment ID) ON DELETE CASCADE ON UPDATE CASCADE)
[UPDATE] I have also tried adapting the answer for this question to disable foreign keys check and enable it again after the operations have been carried out; however, the following code still has the same error as above:
public int returnExperimentNo() {
int maxID = 0;
Connection con = con();
try {
/**
* Temporary disable foreign keys check to perform operations that would fail if these checks were enabled
*/
Statement stmt = con.createStatement();
stmt.execute("SET FOREIGN_KEY_CHECKS = 0");
stmt.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
String sql = "SELECT MAX(`Experiment ID`) AS max_id FROM Experiments";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
if(rs.next()) {
maxID = rs.getInt("max_id");
}
} catch (Exception e) {
System.out.println("Error: " + e);
}
try {
/**
* Enable foreign keys check
*/
Statement stmt = con.createStatement();
stmt.execute("SET FOREIGN_KEY_CHECKS = 1");
stmt.close();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return maxID;
}
Since Iā€™m still new to MySQL, can someone help me correct this? Thank you so much for your help!

JPA number generators without primary key and general use

Environment: OpenJPA2.x, Tomcat webapp, Mariadb but may be changed. This is not Hibernate or Spring webapp.
I have read few topics already such as this:
Hibernate JPA Sequence (non-Id)
I have few entity classes with someNumber non-primary key field, some have someNumber and someNumberB twin columns. Fields have a constraint UNIQUE(someNumber) and UNIQUE(someNumberB), primary key composite is PRIMARY(server_id, code). I need a numeric value before commiting row inserts.
If I understood other topics I cannot use JPA #generator tags. I am forced to implement an old-school utility method. This is a method I did, it takes a fresh db connection so it's always run in a separate transaction.
public synchronized static long getGeneratorValue(String genName, int incStep) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
if (genName==null) genName="app";
// try few times before give up, another node may have updated a value in-between. Create a new transaction from db connection pool.
for(int idx=0; idx<3; idx++) {
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
rs = stmt.executeQuery(String.format("Select value From generators Where name='%s'", genName));
if (!rs.next()) throw new IllegalArgumentException("Invalid generator name " + genName);
if (incStep==0)
return rs.getLong("value"); // return an existing value
long oldValue = rs.getLong("value");
long newValue = oldValue+incStep;
int rowCount = stmt.executeUpdate(String.format("Update generators Set value=%d Where name='%s' and value=%d", newValue, genName, oldValue));
if (rowCount>0) {
conn.commit();
return newValue;
}
close(rs, stmt, conn);
conn=null;
}
throw new IllegalArgumentException("Obtaining a generator value failed " + genName);
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}
I am not fully happy with this, especially a failsafe foreach_loop against another Tomcat node updated generator value in-between concurrently. This loop may fail on busy workloads.
Could I use DB auto_increment column as a general purpose number generator, I suppose it tolerates better concurrency? If this locks a database to MariaDB,MySQL or similar I can live with that for now.
Value must be a numeric field for legacy purpose, I cannot use GUID string values.
I was thinking about using DB auto_increment column and came up with this utility function. I probably go with this implementation or do StackOverflow community have better tricks available?
CREATE TABLE generatorsB (
value bigint UNSIGNED NOT NULL auto_increment,
name varchar(255) NOT NULL,
PRIMARY KEY(value)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_swedish_ci;
// name=any comment such as an entityClass name, no real application use
public static long getGeneratorBValue(String name) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
String sql = String.format("Insert Into generatorsB (name) Values('%s')", name);
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
int rowCount = stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
if (rowCount>0) {
rs = stmt.getGeneratedKeys();
if (rs.next()) {
long newValue = rs.getLong(1);
if (newValue % 5 == 0) {
// delete old rows every 5th call, we don't need generator table rows
sql = "Delete From generatorsB Where value < "+ (newValue-5);
stmt.close();
stmt = conn.createStatement();
stmt.executeUpdate(sql, Statement.NO_GENERATED_KEYS);
}
conn.commit();
return newValue;
}
}
throw new IllegalArgumentException("Obtaining a generator value failed");
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}

ResultSet is Closed

Following is my Table Definition:
create Table alarms(
alarmId int primary key identity(1,1),
alarmDate varchar(50) not null,
alarmText varchar(50) not null,
alarmStatus varchar(10) Check (alarmStatus in(-1, 0, 1)) Default 0
);
Secondly here are some of my methods i'm using:
public void restartDatabase(){
try{
Class.forName(Settings.getDatabaseDriver());
connection = DriverManager.getConnection( Settings.getJdbcUrl() );
statement = connection.createStatement();
}
catch(Exception e){
e.printStackTrace();
}
}
public ResultSet executeQuery(String query){
ResultSet result = null;
try {
result = statement.executeQuery(query);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
public void closeDatabase() {
try {
if ((statement != null) && (connection != null)) {
statement.close();
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
What i want to do is to get all the alarmId's from the table where date is equal to the given date and then against each alarmId i want to update its status to given status:
public static void updateAlarmStatus(int status) {
ResultSet rs = null;
database.restartDatabase();
try {
rs = database
.executeQuery("Select alarmId from alarms where alarmDate = '"
+ Alarm.getFormattedDateTime(DateFormat.FULL,
DateFormat.SHORT) + "'");
while (rs.next()) {
database.executeUpdate("update alarms set alarmStatus = '"+status+"' where alarmId = '"+rs.getString("alarmId")+"'");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
database.closeDatabase();
}
}
But it generates the Error that Result Set is Closed.
I Goggled it and came to know that a result set automatically closes when we try to execute another query inside it
and it needs to restart the connection.
i tried calling restartDatabase() method that is creating new connection but still getting the same error.
I'm guessing executeUpdate uses the same instance variable for its Statement as the query uses. When you create a new Statement and assign it to the variable, nothing is referring to the old one, so it gets cut loose and becomes subject to garbage-collection. During garbage collection the statement's finalizer is invoked, closing it. Closing the statement makes the ResultSet it created close as well.
You shouldn't be sharing these Statement variables between different queries and updates. The statement should be a local variable and not a member of an object instance.
Also, result Sets should always be local variables, they shouldn't be passed outside the method where they're created. The resultSet is a reference to a cursor, it doesn't actually hold any data. Always have your code read from the resultSet and populate some data structure with the results, then return the data structure.
You can also select and change all alarmIds at once:
rs = database.
executeQuery("Select group_concat(distinct alarmId) as alarmIds from alarms group by alarmDate having alarmDate = '"
+ Alarm.getFormattedDateTime(DateFormat.FULL,
DateFormat.SHORT) + "'");
while (rs.next()) { // there will be only one result
database.executeUpdate("update alarms set alarmStatus = '"+status+"' where alarmId in ("+rs.getString("alarmIds")+")");
}

why does executeUpdate return 1 even if no new row has been inserted?

here is my very simple table (Postgres):
CREATE TABLE IF NOT EXISTS PERFORMANCE.TEST
(
test text NOT NULL UNIQUE
);
if I try to insert a String using the command below FROM the database,everything works as expected, not surprisingly a new row appears in the DB.
insert into performance.test (test) values ('abbbbaw');
However if I want to insert a String through JDBC, nothing gets inserted, although preparedStatement.executeUpdate() always returns 1.
Below is my method that should be working but it does not. Please tell me if I am missing something obvious.
I want to add that I never get any SQLException.
private void storePerformance() {
Connection conn= initializePerformanceConnection();
if (conn!= null) {
PreparedStatement insertPS = null;
try {
insertPS = conn.prepareStatement("insert into performance.test (test) values (?)");
insertPS.setString(1, queryVar);
int i = insertPS.executeUpdate();
LogManager.doLog(LOG, LOGLEVEL.INFO," numberofrows= "+i);
} catch (SQLException e) {
LogManager.doLog(LOG, LOGLEVEL.INFO,"Inserting query failed = "+queryVar,e);
}finally{
if(insertPS != null){
try {
insertPS.close();
} catch (SQLException e) {
LogManager.doLog(LOG, LOGLEVEL.INFO,"Closing PreparedStatement failed = "+queryVar,e);
}
}
try {
conn.close();
} catch (SQLException e) {
LogManager.doLog(LOG, LOGLEVEL.INFO,"Closing performanceConnection failed= "+ queryVar, e);
}
}
}
}
that was missing:
conn.commit();
(after the executeUpdate())
actually a new row was inserted but the DB rolled back immediately.
executeupdate is for a 'update table set column = value so on'. For insert just call execute of PreparedStatement.

SQL batch update - rollback on "CREATE TABLE"?

I have a method for upgrading DB:
private void executeUpdateBatch(String... sql) throws SQLException {
JdbcConnection connJbdc = new JdbcConnectionImpl();
Connection conn = connJbdc.getConnection();
conn.setAutoCommit(false);
Statement st = conn.createStatement();
for(String s : sql) {
st.addBatch(s);
}
try {
// execute the batch
int[] updateCounts = st.executeBatch();
} catch (BatchUpdateException e) {
int[] updateCounts = e.getUpdateCounts();
checkUpdateCounts(updateCounts);
try {
conn.rollback();
} catch (Exception e2) {
}
throw new SQLException(e);
}
// since there were no errors, commit
conn.commit();
st.close();
conn.close();
}
And upgrade method:
public void upgradeTo5() throws SQLException {
executeUpdateBatch("CREATE TABLE project ("
+ "id INT(10) unsigned NOT NULL auto_increment, "
+ "title VARCHAR(255) NOT NULL, "
+ "date_from DATE NULL, date_to DATE NULL,"
+ "active BIT NOT NULL, PRIMARY KEY (id))",
"INSERT INTO project(titlea) VALUES('test1')");
}
An error is in INSERT just for testing rollback.
Well, the problem now is that it does not rollbacks CREATE TABLE project. Table is InnoDB. Any suggestions?
This is not supported by MySQL/InnoDB. All DDL statements (CREATE TABLE, ALTER TABLE, CREATE INDEX, DROP ...) always happen outside of transaction control.
This is a weak point that IIRC Postgres can handle better, but with MySQL you have to work around that by reverting the changes yourself in case of rollbacks.

Categories

Resources