ziqiangliang commented on issue #7378:
URL: https://github.com/apache/gravitino/issues/7378#issuecomment-2973810871
To eliminate connection leak issues, I redesigned the transactional API for
composing multiple operations.
The current utility methods like:
```java
/**
* This method is used to perform a database operation without a commit and
fetch the result. If
* the operation fails, will throw the RuntimeException.
*/
public static <T, R> R doWithoutCommitAndFetchResult(Class<T> mapperClazz,
Function<T, R> func) {
T mapper = SqlSessions.getMapper(mapperClazz);
return func.apply(mapper);
}
/**
* This method is used to perform a database operation without a commit. If
the operation fails,
* will throw the RuntimeException.
*/
public static <T> void doWithoutCommit(Class<T> mapperClazz, Consumer<T>
consumer) {
T mapper = SqlSessions.getMapper(mapperClazz);
consumer.accept(mapper);
}
/**
* This method is used to perform multiple database operations with a
commit. If any of the
* operations fail, the transaction will totally roll back.
*/
public static void doMultipleWithCommit(Runnable... operations) {
try (SqlSession session = SqlSessions.getSqlSession()) {
try {
Arrays.stream(operations).forEach(Runnable::run);
SqlSessions.commitAndCloseSqlSession();
} catch (Exception e) {
SqlSessions.rollbackAndCloseSqlSession();
throw e;
}
}
}
```
allow users to perform operations without committing, relying on external
transactional control (e.g., via `doMultipleWithCommit`). However, they can be
**misused independently** like this:
```java
// Dangerous usage - no commit or session close guaranteed
SessionUtils.doWithoutCommit(SomeMapper.class, mapper -> mapper.insert(...));
```
which risks leaking database connections and leaving transactions
uncommitted.
### ✅ Proposed New API Design
Introduce a structured transactional operation wrapper:
```java
/**
* Creates a database operation with the given mapper and function, to be
executed without committing.
*/
public static <T, R> Operation<T, R> callWithoutCommit(
Class<T> mapperClass, Function<T, R> function) {
return new Operation<>(mapperClass, function);
}
/**
* Creates a database operation that performs an action using the given
mapper, without returning
* a result or committing the transaction.
*/
public static <T> Operation<T, Void> opWithoutCommit(Class<T> mapperClass,
Consumer<T> consumer) {
return new Operation<>(
mapperClass,
mapper -> {
consumer.accept(mapper);
return null;
});
}
/**
* Executes multiple database operations within a single transaction. If all
succeed, commits.
* If any fail, rolls back.
*/
public static List<Object> doMultipleWithCommit(Operation<?, ?>...
operations) {
try (SqlSession session = SqlSessions.getSqlSession()) {
try {
List<Object> results = new ArrayList<>();
for (Operation<?, ?> op : operations) {
results.add(op.execute());
}
SqlSessions.commitAndCloseSqlSession();
return results;
} catch (Throwable t) {
SqlSessions.rollbackAndCloseSqlSession();
throw t;
}
}
}
/**
* Represents a database operation that can be executed with a given mapper.
*/
public static class Operation<T, R> {
private final Class<T> mapperClass;
private final Function<T, R> function;
private Operation(Class<T> mapperClass, Function<T, R> function) {
this.mapperClass = mapperClass;
this.function = function;
}
R execute() {
T mapper = SqlSessions.getMapper(mapperClass);
return function.apply(mapper);
}
}
```
Example usage:
```java
int[] deleteCount1 = new int[1];
int[] deleteCount2 = new int[1];
List<Object> results = SessionUtils.doMultipleWithCommit(
SessionUtils.opWithoutCommit(ModelVersionMetaMapper.class, mapper ->
deleteCount1[0] =
mapper.deleteModelVersionMetasByLegacyTimeline(legacyTimeline, limit)),
SessionUtils.callWithoutCommit(ModelVersionAliasRelMapper.class, mapper ->
deleteCount2[0] =
mapper.deleteModelVersionAliasRelsByLegacyTimeline(legacyTimeline, limit))
);
System.out.println("Deleted ModelVersionMeta count: " + deleteCount1[0]);
System.out.println("Deleted ModelVersionAliasRel count: " + deleteCount2[0]);
```
The access modifier of Operation#execute is package-private, so it cannot be
called outside of doMultipleWithCommit. This prevents issues like connection
leaks and transaction hangs.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]