Alter Oracle session variables outside of a Hibernate transaction - java

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'")
);
}

Related

Spring Transaction handling with EclipseLink

I am new to databases and transactions. I find a lot of different information up-to-date and am currently trying to organize my thoughts.
Regarding the context, I'm trying to test the current isolation level with the SQL Server, but I don't succeed.
For this I use Spring Transaction together with Eclipse Link. I found some information about Spring transactions (https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth). However, there is still a concept of the works unit and now I don't know what is currently being used (https://wiki.eclipse.org/Introduction_to_EclipseLink_Transactions_(ELUG)#Unit_of_Work_Architecture)
What I am trying to test:
I have an Entity User (id, firstname, lastname). I have an entry in table id = 1, firstname = foo lastname = bar
I have a service and transaction. IMHO default is isolation READ_COMMIT for SQL Server
#Transactional
public User updateUser(){
User updateUser = new User(1, "new firstname", "new lastname");
User updatedUser = userRepository.save(updateUser); --> em.merge
return updatedUser;
}
So far so good. What I do not understand now. I set a breakpoint in the return.
At the same time I opened a second SQL client and executed the following SQL.
SET IMPLICIT_TRANSACTIONS ON
UPDATE
User
SET
lastname = 'complete new',
WHERE
id = 1
COMMIT TRAN
What I would expect is that the SQL statement will wait for the Spring transaction to complete. BUT this is currently not the case, the SQL statement is simply carried out.
Then lastname is in the table lastname "complete new" then I resume the breakpoint and then the lastname is "new lastname". This behavior I cannot understand. Is this normal or is this because of the unit work of eclipse link?
EclipseLink buffers SQL statements that need to be executed for as long as possible in order to reduce the lock time in the RDBMS. In your particular case the JDBC driver will receive the UPDATE statement when Spring Data JPA commits the transaction. You can verify it by enabling SQL logging in EclispeLink: stackoverflow.com/q/2374395/17695211
After enabling SQL logging you'll see that there won't be any SQL debug output in the console at your breakpoint. It will appear after the return. If you really want to see the locking effect, you need to write the repository without Spring Data JPA with a #PersistenceContext-injected EntityManager and call EntityManager.flush which will flush SQL statement buffer of EclipseLink to the JDBC driver before the breakpoint.
Alternatively (and if you feel adventurous), you may try looking for a place in the EclipseLink source code where it executes the corresponding PreparedStatement, and set the breakpoint right after it.

How to start an IMMEDIATE TRANSACTION in SQLITE JDBC

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

jdbc autocommit(false) doesnt work

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()

MyBatis Batch Insert/Update For Oracle

I've recently started learning to use myBatis.I am now facing such a scenario, I need to constantly fetch a new list of Objects through WebService, then for this list, I need to insert/update each object into the oracle DB table through myBatis.
The tricky part is, I cannot simply do a batch insert every time, because some of the objects might already exist in DB, for these records, I need to update the fields of them instead of a new insertion.
My current solution might be very stupid, using Java, build the list of Object from webservice, loop through each of them, do a myBatis select, if it is not a null(already exists in the db), then do a myBatis update; otherwise, do a myBatis insert for this new object.
The function is achieved. But my technical lead says it is very low-efficient, since doing a for loop using Java and insert/update one by one will consume a lot of system resource. He advised me to do batch insert using myBatis by passing a list of objects in.
Batch insertion in myBatis is straightforward, however, since I am not purely inserting(for existing records I need to do update), I don't think batch insert is appropriate here. I've googled a while for this, and realized maybe I will need to use "merge" instead of "insert" (for Oracle).
The examples I googled out for merge in myBatis is only for one object, not in a batch. Thus I want to find out whether experts could offer me some examples on how to do a batch-merge in MyBatis( The correct way to write a Mapper)?
The accepted answer is not the recommended way of handling batch operations. It does not show true batch statements since the batch executor mode should be used when opening a session. See this post in which a code contributor recommended that the proper way to batch update (or insert) is to open a session in batch mode and repeatedly call update (or insert) for a single record.
Here's what works for me:
public void updateRecords(final List<GisObject> objectsToUpdate) {
final SqlSession sqlSession = MyBatisUtils.getSqlSessionFactory().openSession(ExecutorType.BATCH);
try {
final GisObjectMapper mapper = sqlSession.getMapper(GisObjectMapper.class);
for (final GisObject gisObject : objectsToUpdate) {
mapper.updateRecord(gisObject);
}
sqlSession.commit();
} finally {
sqlSession.close();
}
}
Do not use foreach in your update/insert and ensure that it only updates/inserts a single record. I was running into unsolvable oracle errors by doing it according to the accepted answer (invalid character, statement not ended, etc.). As the linked post indicates, the update (or insert) shown in the accepted answer is actually just a giant sql statement.
In my case also there is same scenario. I used for loop to check whether this record exists in databse or not and then according to that I added this object in to two arraylist for insert or update.
And then used batch for insert and update after for loop for that to list.
here is ex. for update according to different where condition
1] this is for update
<foreach collection="attendingUsrList" item="model" separator=";">
UPDATE parties SET attending_user_count = #{model.attending_count}
WHERE fb_party_id = #{model.eid}
</foreach>
2] this is for insert
<insert id="insertAccountabilityUsers" parameterType="AccountabilityUsersModel" useGeneratedKeys="false">
INSERT INTO accountability_users
(
accountability_user_id, accountability_id, to_username,
record_status, created_by, created_at, updated_by, updated_at
)
VALUES
<foreach collection="usersList" item="model" separator=",">
(
#{model.accountabilityUserId}, #{model.accountabilityId}, #{model.toUsername},
'A', #{model.createdBy}, #{model.createdAt}, #{model.updatedBy}, #{model.updatedAt}
)
</foreach>
</insert>
In dao method declare as
void insertAccountabilityUsers(#Param("usersList") List<AccountabilityUsersModel> usersList);
Update
Here is my batch session code
public static synchronized SqlSession getSqlBatchSession() {
ConnectionBuilderAction connection = new ConnectionBuilderAction();
sf = connection.getConnection();
SqlSession session = sf.openSession(ExecutorType.BATCH);
return session;
}
SqlSession session = ConnectionBuilderAction.getSqlSession();
Actually I already given full example here for this question
In oracle if you want to execute multiple statements at one time you have to enclose your statements in "begin" and "end" block. So try to add attributes to foreach as below. This will definitely work.
<foreach collection="customerList" item="object" open="begin" close=";end;" separator=";">
UPDATE customer SET isActive = #{object.isactive}
WHERE customerId= #{object.customerId}
</foreach>

java.sql.SQLException: The method 'rollback' can't be called when a global transaction is active

I want to use java invoke a oracle store procedure to return OracleResultSet, the store procedure have execute delete, insert and select operate(involves two tables). I set auto commit false and when execute commit() occur the error like the Title.
The code like below:
conn.setAutoCommit(false);
resultSet = getResultByInvokeSP(conn);
conn.commit();
after commit use
conn.setAutoCommit(true);
Remove your calls to setAutoCommit() and commit(). The error suggests you have another framework which is handling your transactions for you.

Categories

Resources