Reputation: 14278
Suppose think of a scenario:
function a(){
create savepoint s1
insert x1
insert x2
b()
insert x4
if we get an error rollback to s1
else commit.
}
function b(){
create savepoint s2
insert x3
if we get an error rollback to s2
else commit
}
My question is suppose my b() passes with no error and and there is a problem while inserting x4 so i fired rollback. So my question whether it will also revert the things which are inserted as part of the b() i.e. x3.
Upvotes: 0
Views: 878
Reputation: 109239
Method b()
shouldn't commit at all, the commit()
should only be allowed after everything is done.
You should modify your scenario to:
function a(){
create savepoint s1
insert x1
insert x2
b()
insert x4
if we get an error rollback to s1
else commit.
}
function b(){
create savepoint s2
insert x3
if we get an error rollback to s2
}
The only difference is the remove of the commit branch in b()
. The Connection
will maintain savepoint s2 until the rollback to s1
or the commit.
Be aware that if function a()
itself is also part of a larger transaction, the else commit
there should be removed as well. If a()
is the start of the transaction, then you should just rollback (not rollback to s1
).
If you want these to be independently callable, or composable, then I'd suggest you use some form of a transaction manager that you pass into the method call.
Say something along the lines of
interface TransactionManager() {
void commit();
void rollback();
TransactionManager subTransaction();
}
class RootTransactionManager() implements TransactionManager {
private final Connection connection;
RootTransactionManager(Connection connection) {
this.connection = connection;
}
public void commit() {
connection.commit();
}
public void rollback() {
connection.rollback();
}
public TransactionManager subTransaction() {
return new SubTransactionManager(this);
}
Connection getConnection() {
return connection;
}
}
class SubTransactionManager implements TransactionManager() {
private final RootTransactionManager manager;
private final SavePoint savePoint;
SubTransactionManager (RootTransactionManager manager) {
this.manager = manager;
savePoint = manager.getConnection().setSavePoint();
}
public void commit() {
// do nothing (semantics of Connection.releaseSavePoint not useful for nesting)
}
public void rollback() {
connection.rollback(savePoint);
}
public TransactionManager subTransaction() {
return new SubTransactionManager(manager);
}
}
You can use this to implement your methods as:
void someOtherMethod() {
Connection connection; // = ...
a(new RootTransactionManager(connection));
}
void a(TransactionManager tm) {
try {
// do stuff
b(tm.subTransaction);
// do stuff
tm.commit();
} catch (SQLException ex) {
tm.rollback();
}
}
void b(TransactionManager tm) {
try {
// do stuff
tm.commit();
} catch (SQLException ex) {
tm.rollback();
}
}
This is just a quick sketch and I might come up with something completely different if I actually took more time for it.
Upvotes: 1
Reputation: 30849
It will revert the things before the last save point i.e. Insert X1, X2 and b()'s modifications will not be reverted.
Ideally, to maintain the atomicity of transaction, we should not issue commit statements in between it. Commit() should only be issued if all the operations get completed successfully. However, if something fails, rollback should be issued (typically in catch block).
Upvotes: 0