减少提交并在jdbc调用中一次插入大容量记录



我已经实现了数据库日志记录,将我的java weblogic门户应用程序中的某些详细信息记录到Oracle数据库中。为此,我使用连接池来获取连接,并使用它对存储过程进行jdbc调用。

我有一个静态的java方法logService,它获取连接对象并调用SP。在我的java应用程序中,这个logservice方法是从不同的地方调用的,相关的详细信息在参数中传递,inturn传递给SP调用。

因此,每个访问门户的用户将有大约10-20个logservice方法调用。因此,我将打10-20个SP电话,每次打SP时我都必须提交。

但问题是,我的DBA建议不要对每个事务进行提交,而是一次提交所有10-20个事务,因为提交频率很高。我该如何做到这一点?如果不清楚,我可以发布一个示例代码。

是的,我已经有过滤器为我的网络应用程序如下,

public final class RequestFilter implements Filter{
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
 try {
      ((HttpServletResponse) response).sendRedirect(redirectUrl);
  if (chain != null) {
            chain.doFilter(request, response);
        }
     }
 finally { // commit transaction here }
 }
}

重定向到重定向URL后,我在应用程序中的许多地方调用logservice,如下所示,

DBLog.logService(param1, param2); // static method call

此日志服务使用连接池对象准备对SP的调用。我只是在下面提供一个简单的示例代码来说明我是如何做的。

public static void logService(String param1, String param2) {
try {
con=getConnection();
stmt = con.prepareCall("{call DB_LOG_SP (?, ?}");
stmt.setString(1, param1);
stmt.setString(2, param2);
stmt.execute();
stmt.close();
}finally {
           stmt.close();
           con.close();
        } 
 }

但是,由于我在SP调用后关闭了连接,我如何在doFilter方法内的finally{}块中提交事务?

过滤方法——

public class DBLog {
static Connection con = null;
    static PreparedStatement stmt = null;
static {
        try{
        new net.verizon.whatsnext.dblog.JDCConnectionDriver("oracle.jdbc.driver.OracleDriver", 
        "jdbc:oracle:thin:@localhost:7001:xe","user", "password"); 
    con=getConnection(); // Getting connection from Connection pool
    }catch(Exception e){}
    }
 public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:jdc:jdcpool"); 
    }

public static void logService(String param1, String param2) {
    ...
    finally {
               stmt.close();
            } 
     }
 public static void closeTrans() {
    con.commit();
    con.close();
    }
} //End of DBLog class

 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
    ...
     finally { 
          DBLog.closeTrans();
         }
    }

因此,在doFilter方法中的finally块中提交后,我最终关闭了连接。我做得对吗?它行得通吗?

首先,不要在每个请求上打开连接,而是重用连接池(可能是DataSource)中的连接,并将其放在共享空间中-在您的情况下,最简单的方法是利用静态ThreadLocal字段。

过滤器内:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
  Connection conn = obtainConnection(); // obtain connection and store it ThreadLocal
  conn.setAutoCommit(false);
  try {
    chain.doFilter(request, response);
  } finally {
    conn.commit();
  }
}

在您的记录器类中:

public static void logService(String param1, String param2) {
  Connection conn = getConnection(); // obtain thread-local connection
  // your database statements go here
}

一般情况下,应避免使用静力学。也许可以考虑重新考虑你的日志记录方法。

考虑每个请求使用一个事务。您可以创建一个Filter,在处理请求之前打开数据库事务,然后提交。这样就可以了。

事务性划分,就像安全性和日志记录一样,可以被视为代码的装饰。

假设您有一个接口BusinessTask,它是当前所有数据库操作所实现的接口:

public interface BusinessTask {
   public void doWork();
}

例如,您有一个AddSavingsAccountTaskTransferMoneyTask

现在,最初您可以以这样一种方式实现每个任务,即每个任务单独提交(或回滚)。但是,如果你想创建一个任务,包括创建一个账户并立即转账,那么你就会遇到麻烦,因为现在你需要改变交易界限。

因此,一种方法是定义一个TransactionDecorator,您可以在其中进行事务划分。

public class TransactionDecorator implements BusinessTask{
    private final BusinessTask task;
    public TransactionDecorator(BusinessTask task){
        this.task = task;
    }
    @Override
    public void doWork() {
        //beging transaction
        task.doWork();
        //commit or rollback
    }
}

然后你可以简单地用事务划分来装饰你现有的任务:

final AddSavingsAccount addSavingsAccountTask = new AddSavingsAccount();
final TransferFunds transferFundsTask = new TransferFunds();
BusinessTask transaction = new TransactionDecorator(new BusinessTask(){
    @Override
    public void doWork() {
        addSavingsAccountTask.doWork();
        transferFundsTask.doWork();
    }
});
transaction.doWork(); //all the magic happens here

现在,我只是在这里提出一个模式。在代码中实现这一点的方式会因您的设计和代码的结构而异。

进一步参考

  • 装饰图案
  • 代理模式