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]

Reply via email to