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.
Related
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!
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.
I try to put some Data in my H2 database but I'm a total noob in databases so it throws error over error since more than a hour.
Normaly I can fix it somehow but now I got a new problem I try to use
getGeneratedKeys() first I tried to use AUTO_INCREMENT(1,1) but that didn't works too function but it won't work rigth.
The exception my programm throws is
org.h2.jdbc.JdbcSQLException: Funktion "GETGENERATEDKEYS" nicht gefunden
Function "GETGENERATEDKEYS" not found; SQL statement:
insert into logTbl values( getGeneratedKeys(),Webservice->startThread0: Thread0) [90022-173]
an my database function looks like this
public void createTable(String Log) {
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
System.err.println("TREIBER FEHLER");
e.printStackTrace();
}
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:h2:~/DBtest/Logs");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS logTbl(ID INT PRIMARY KEY, LOG VARCHAR(255))");
//stat.execute("insert into test values(1, 'Hello')");
for (int i = 0; i < 20; i++) {
stat.execute("insert into logTbl values( getGeneratedKeys()," + Log + ")");
}
stat.close();
conn.close();
} catch (SQLException e) {
System.err.println("SQL FEHLER");
e.printStackTrace();
}
}
hope you can help me to fix my error as I said I'm totaly new and just had some code example as "tutorial" because I don't found a good tutorial
If you want to automatically generate primary key values, you need to first change the definition of your table:
CREATE TABLE IF NOT EXISTS logTbl
(
ID integer AUTO_INCREMENT PRIMARY KEY,
LOG VARCHAR(255)
);
You should also use a PreparedStatement rather than concatenating values.
So your Java code would look something like this:
String insert = "insert into logTbl (log) values(?)";
PreparedStatement pstmt = connection.prepareStatement(insert, Statement.RETURN_GENERATED_KEYS);
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
long id = -1;
while (rs.next())
{
rs.getLong(1);
}
It might be that you need to use the overloaded version of prepareStatement() where you supply the column to be returned. Not sure which one works with H2:
prepareStatement(insert, new String[] {"ID"});
Btw: there is nothing "magic" about 255 as the length of a varchar column. There is no performance difference between varchar(500), varchar(20)or varchar(255). You should use the length that you expect you need, not some "magic" limit you think performs better.
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);
}
}
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.