I'm having a little problem with transactions using JDBC.
I want to start an IMMEDIATE transaction which in pure SQL is:
BEGIN IMMEDIATE;
In Java JDBC SQLite, you cannot do this. You can't call BEGIN IMMEDIATE on a statement if you have autocommit enabled. Committing queries will result in an "autocommit is enabled" error.
db = DriverManager.getConnection("jdbc:sqlite:sqlite.db");
// start a transaction using an sql query...
db.createStatement().execute("BEGIN IMMEDIATE");
// create another statement because this is running from another method...
stmt = db.createStatement();
stmt.executeUpdate("UPDATE table SET column='value' WHERE id=1");
// this will cause an error(exception): AUTOCOMMIT IS ENABLED.
db.commit();
The code above will throw an AUTOCOMMIT IS ENABLED exception.
However, there is also a problem when disabling autocommit because it starts the transaction after using that code. consider the code below:
db = DriverManager.getConnection("jdbc:sqlite:ez-inventory.db");
// doing the createstatement and setautocommit reciprocally still produce the same exception.
db.setAutoCommit(false);
db.createStatement().execute("BEGIN IMMEDIATE");
This code will throw another exception:
[SQLITE_ERROR] SQL error or missing database (cannot start a
transaction within a transaction)
There is a setTransactionIsolation method in the connection but it's not for transaction locking. It's for isolation. I need to start a transaction using any of the SQLite transaction modes: DEFFERED, IMMEDIATE, or EXCLUSIVE
Is this possible with SQLite JDBC?
OK I got it! You should create a Properties object with transaction_mode key and a desired transaction mode value. and put the Properties object as a parameter when your creating your new SQL Connection instance.
import java.sql.*; // <-- bad practice.. just too lazy to put the needed modules one by one for this example
public void immediate_transaction_example() throws SQLException {
// create a properties with a transaction_mode value
Properties sqlprop = new Properties();
properties.put("transaction_mode", "IMMEDIATE"); // <-- can be DEFERRED, IMMEDIATE, or EXCLUSIVE
db = new DriverManager.getConection("jdbc:sqlite:sqlite.db", sqlprop); // <-- pass the properties to the new sql connection instance.
db.setAutoCommit(false); // <-- this will automatically begin the transaction with the specified transaction mode...
// other new transactions attempts with immediate transaction mode will be blocked until the connection is closed.
try {
// proceed the transactions here...
db.createStatement().execute("INSERT INTO table (id, value) VALUES (1, 'myvalue')");
db.createStatement().execute("INSERT INTO table (id, value) VALUES (2, 'myvalue')");
// no errors proceed
db.commit();
} catch (SQLException ex) {
// there is an error!
db.rollback();
}
db.close() // <-- you need to close the connection for sqlite to create a new immediate transaction.
}
Note: This uses xerial's sqlite-jdbc module.
Module Link: https://github.com/xerial/sqlite-jdbc
Related
We've been using a pattern like this for a while to ensure a specific operation is executed with BATCH NOWAIT, for performance reasons.
try {
session.createSQLQuery("ALTER SESSION SET COMMIT_LOGGING='BATCH' COMMIT_WAIT='NOWAIT'").executeUpdate();
// Do the operation (which also calls transaction.commit())
return callback.apply(session);
} finally {
session.createSQLQuery("ALTER SESSION SET COMMIT_LOGGING='IMMEDIATE' COMMIT_WAIT='WAIT'").executeUpdate();
}
This has worked fine in Hibernate 4. As of Hibernate 5, the last statement fails because it's not inside a transaction (as it's just been committed).
javax.persistence.TransactionRequiredException: Executing an update/delete query
It isn't an update or a delete, but executeUpdate() is the only method you can call to execute this statement without returning any rows. It shouldn't need to be in a transaction since session variables apply to the entirety of the connection, and it does need to be executed to restore the session variables because a connection pool is in use.
I've tried using one of the query methods instead, but this statement has -1 rows, and it won't let me stack SELECT 1 FROM DUAL on the end.
Is there any way to execute a native query from Hibernate that's neither update/delete or results-returning, outside of a transaction?
Using the underlying Connection directly bypasses Hibernate's checks and allows me to execute such a statement in peace.
try {
session.doWork(conn ->
conn.createStatement().execute("ALTER SESSION SET COMMIT_LOGGING='BATCH' COMMIT_WAIT='NOWAIT'")
);
return callback.apply(session);
} finally {
session.doWork(conn ->
conn.createStatement().execute("ALTER SESSION SET COMMIT_LOGGING='IMMEDIATE' COMMIT_WAIT='WAIT'")
);
}
We have a function in our application that cleans up the database and resets the data, we call a method cleanup() which at first deletes all the data from the database and then calls a sql script file to insert all the necessary default data of our application. Our application supports Oracle - MySQL and MSSQL, the function works fine on both Oracle and MySQL but doesn't work as it supposed to on MSSQL.
The problem is that it is clearing all the data from the database but not inserting the default data, the first section of the method works fine but the second section doesn't get committed to the database. Here is the function:
public boolean cleanup(...){
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
// delete and drop sql queries here...
tx.commit();
session.close();
// end of first section
session = sessionFactory.openSession();
tx = session.beginTransaction();
// insert default data sql queries here...
tx.commit();
session.close();
// end of second section
}
The database gets cleared successfully, but the default data is not being inserted to the database. Please let me know what am I doing wrong here. I tried doing both delete and insert sections in one transaction with no luck.
I have a custom ExecuteListener that executes additional statements before the statement JOOQ is currently looking at:
#Override
public void executeStart(ExecuteContext ctx) {
if (ctx.type() != READ) {
Timestamp nowTimestamp = Timestamp.from(Instant.now());
UUID user = auditFields.currentUserId(); // NOT the Postgres user!
Connection conn = ctx.connection();
try (Statement auditUserStmt = conn.createStatement();
Statement auditTimestampStmt = conn.createStatement()) {
// hand down context variables to Postgres via SET LOCAL:
auditUserStmt.execute(format("SET LOCAL audit.AUDIT_USER = '%s'", user.toString()));
auditTimestampStmt.execute(format("SET LOCAL audit.AUDIT_TIMESTAMP = '%s'", nowTimestamp.toString()));
}
}
}
The goal is to provide some DB-Triggers for auditing with context information. The trigger code is given below [1] to give you an idea. Note the try-with-resources that closes the two additional Statements after execution.
This code works fine in the application server, where we use JOOQ's DefaultConnectionProvider and ordinary JOOQ queries (using the DSL), no raw text queries.
However, in the migration code, which uses a DataSourceConnectionProvider, the connection is already closed when JOOQ attempts to execute its INSERT/UPDATE query.
The INSERT that triggers the exception looks like this:
String sql = String.format("INSERT INTO migration.migration_journal (id, type, state) values ('%s', 'IDD', 'SUCCESS')", UUID.randomUUID());
dslContext.execute(sql);
and this is the exception raised:
Exception in thread "main" com.my.project.data.exception.RepositoryException: SQL [INSERT INTO migration.migration_journal (id, type, state) values ('09eea5ed-6a68-44bb-9888-195e22ade90d', 'IDD', 'SUCCESS')]; This statement has been closed.
at com.my.project.shared.data.JOOQAbstractRepository.executeWithoutResult(JOOQAbstractRepository.java:51)
at com.my.project.demo.data.migration.JooqMigrationJournalRepositoryUtil.addIDDJournalSuccessEntry(JooqMigrationJournalRepositoryUtil.java:10)
at com.my.project.demo.data.demodata.DemoDbInitializer.execute(DemoDbInitializer.java:46)
at com.my.project.shared.data.dbinit.AbstractDbInitializer.execute(AbstractDbInitializer.java:41)
at com.my.project.demo.data.demodata.DemoDbInitializer.main(DemoDbInitializer.java:51)
Caused by: org.jooq.exception.DataAccessException: SQL [INSERT INTO migration.migration_journal (id, type, state) values ('09eea5ed-6a68-44bb-9888-195e22ade90d', 'IDD', 'SUCCESS')]; This statement has been closed.
at org.jooq.impl.Tools.translate(Tools.java:1690)
at org.jooq.impl.DefaultExecuteContext.sqlException(DefaultExecuteContext.java:660)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:354)
at org.jooq.impl.DefaultDSLContext.execute(DefaultDSLContext.java:736)
at com.my.project.demo.data.migration.JooqMigrationJournalRepositoryUtil.lambda$addIDDJournalSuccessEntry$0(JooqMigrationJournalRepositoryUtil.java:12)
at com.my.project.shared.data.JOOQAbstractRepository.executeWithoutResult(JOOQAbstractRepository.java:49)
... 4 more
Caused by: org.postgresql.util.PSQLException: This statement has been closed.
at org.postgresql.jdbc.PgStatement.checkClosed(PgStatement.java:647)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:163)
at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:158)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java)
at org.jooq.tools.jdbc.DefaultPreparedStatement.execute(DefaultPreparedStatement.java:194)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:408)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:340)
... 7 more
I traced this back to DataSourceConnectionProvider.release() and therefore connection.close() being called via auditUserStmt.close(). Note that it is critical that the SET commands are executed on the same Connection. I would be fine with obtaining a Statement from JOOQ's connection that I have to close myself, but I can't find a JOOQ method to obtain such an "unmanaged" statement.
We're using the Hikari connection pool, so the connection acquired by JOOQ is a HikariProxyConnection. From within the migration code, the DataSource is configured only minimally:
HikariDataSource dataSource = new HikariDataSource();
dataSource.setPoolName(poolName);
dataSource.setJdbcUrl(serverUrl);
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setMaximumPoolSize(10);
How can I fix my ExecuteListener?
I am using JOOQ 3.7.3 and Postgres 9.5., with the Postgres JDBC Driver 42.1.1.
[1]: Postgres Trigger Code:
CREATE OR REPLACE FUNCTION set_audit_fields()
RETURNS TRIGGER AS $$
DECLARE
audit_user UUID;
BEGIN
-- Postgres 9.6 will offer current_setting(..., [missing_ok]) which makes the exception handling obsolete.
BEGIN
audit_user := current_setting('audit.AUDIT_USER');
EXCEPTION WHEN OTHERS THEN
audit_user := NULL;
END;
IF TG_OP = 'INSERT'
THEN
NEW.inserted_by := audit_user;
ELSE
NEW.updated_by := audit_user;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
As per #LukasEder's suggestion, I ended up solving this problem with a wrapper around the JDBC Connection instead of with an ExecuteListener.
The main complication in this approach is that JDBC does not provide anything to track the transaction status, and therefore the connection wrapper needs to re-set the context information every time the transaction was committed or rollbacked.
I documented my full solution in this gist, since it's too long to fit in an SO answer.
There is something i don't understand with java.sql.Connection.commit().
I am using Derby(Java DB) as database server.
when I do a setAutoCommit(false) , I expect my query not to work before I explicitly call the commit() method.
but in fact, it still commit even if I don't call commit().
when I call a select * on my table to print the content, I can see that the rows have been added even though i didn't explicitly commit the query.
Could someone give me some explanation please?
con.setAutoCommit(false);
PreparedStatement updateHair = null;
PreparedStatement addMan = null;
try {
String updateString =
"update PERSONNE " +
"set haircolor = 'RED' where haircolor = 'SHAVE'";
String updateStatement =
"insert into personne values " +
"(3,'MICHEL','SHAVE')";
addMan = con.prepareStatement(updateStatement);
addMan.executeUpdate();
updateHair = con.prepareStatement(updateString);
updateHair.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Auto-commit means that each individual SQL statement is treated as a transaction and is automatically committed right after it is executed. The default is for a SQL statement to be committed when it is completed, not when it is executed. A statement is completed when all of its result sets and update counts have been retrieved. In almost all cases, however, a statement is completed, and therefore committed, right after it is executed.
The way to allow two or more statements to be grouped into a transaction is to disable the auto-commit mode.
con.setAutoCommit(false);
When the auto-commit mode is disabled, no SQL statements are committed until you call the method commit explicitly. All statements executed after the previous call to the method commit are included in the current transaction and committed together as a unit.
-- EDIT_1
Updates may be committed because you're closing your Connection without calling rollback().
If a Connection is closed without an explicit commit or a rollback the behaviour depends on database.
It is strongly recommended that an application explicitly commits or
rolls back an active transaction prior to calling the close method. If
the close method is called and there is an active transaction, the
results are implementation-defined.
Connection.close()
I am trying to create events using Java code with hibernate. I verified my syntax for CREATE EVENT in MySQL Workbench, and I verified my Java code with a simple UPDATE query. But when I am trying to use both it just doesn't work. I am not getting any error message, just that 0 rows were affected. My code is as follows:
String sql = "CREATE EVENT IF NOT EXISTS test_event ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 20 SECOND ON COMPLETION PRESERVE ENABLE DO UPDATE my_table SET last_error_message='my test' WHERE ID=17;";
session.beginTransaction();
SQLQuery query = session.createSQLQuery(sql);
int result = query.executeUpdate();
session.getTransaction().commit();
....
session.close();
thanks a lot
How do you know if any new events were created? You can try
show events from <SCHEME_NAME>;
This will show all the events that are registered to the given schema.try printing the session\statement warning stack...
Get jdbc connection from your session: How to get jdbc connection from hibernate session?
Use JDBC to execute DDL
...
Statement stmt = null;
try {
stmt = conn.createStatement();
stmt.executeUpdate(sql);
} catch (SQLException e) {
// ...
} finally {
// close stmt
}
...
First, you need to make sure your database is prepared to execute an event. for that, you need to run the following command.
SHOW PROCESSLIST;
MySQL uses a special thread called event schedule thread to execute all scheduled events.
if you see your process list like the above picture. you need to run the below command to enable MySQL event execution.
SET GLOBAL event_scheduler = ON;
Then when you execute the "SHOW PROCESSLIST;" command you will see the below list which shows the specific thread for event execution in MySQL.
Now you can create your event using any MySQL client interface.