您的位置:首页 > 数据库

编程经验点滴----避免在数据库访问函数中使用 try catch

2016-10-09 14:54 316 查看
看到很多数书中的代码示例,都在数据库访问函数中使用 try catch,误导初学者,很是痛心。

我们来分析一个常见的函数(来自国内某些大公司的代码,反面例子,不可仿效),

1     public int updateData(String sql)     {
2         int resultRow = 0;
3         try{
4             Connection con = ...
5             statement = con.createStatement();
6             resultRow = statement.executeUpdate(sql);
7             ...
8         } catch (SQLException e) {
9             e.printStackTrace();
10         }
11         return resultRow;
12     }


 

 这里所说的函数问题在于,在这样的调用情况下会有问题(请发言者仔细看看这块伪代码):
1) begin database transaction
2) updateData("update user set last_active_time = ...");
3) updateData("insert into ....");
3) ftpSend();
3) sendMail();
4) commit();

updateData() 内部就 try catch 或者 commit/rollback ,问题大了!

 

这里的问题很多:

a) SQL 执行出错后,简单地输出到控制台。没有把出错信息,返回或者通过 throw Exception 抛出。结果很可能是, SQL 运行出错,界面上却提示“操作成功”。

b) 如果代码连续执行多个 update/delete,放在一个 transaction 中。SQL 执行出错后,SQLException 被 catch 住,transaction 控制代码,无法 rollback。

c) 当然还有 SQL 注入问题。这里应该用 PreparedStatement。

 

如果要避免代码“代码中运行出错,界面上却提示:操作成功”的问题,则应该避免在数据库访问函数中使用 try catch。更进一步的,在工具类、dao、service 代码中,都应该禁止用  try catch。

那么,  try catch 应该放在哪里呢?

1) 如果是单机版程序,出错信息应该提示给用户,try catch 放在事件响应函数中。当然了,如果用 transaction , 也在这里 begin/commit/rollback。

2) 如果是 Web MVC 程序,出错信息应该提示给用户,try catch 放在 URL 相应的事件响应 java/C# 代码中。当然了,如果用 transaction , 也在这里 begin/commit/rollback。如果是 Java EE 程序,建议在 filter 中,也放一个 try catch,作为全局的 exception 控制,防止万一有人在 URL 相应的事件响应 java/C# 代码中漏写了try catch 。出错信息也要放在界面上提示给用户看。

3) 如果是定时任务,try catch 应放在定时任务类里,当定时任务类调用 dao/service/工具类的时候,被调用的函数都不应该有 try catch。出错信息应该记录在日志中。

4) 如果不用 MVC 的 jsp/asp.net 程序,try catch 怎么处理,就很麻烦。建议不要用这种软件架构。

 

我觉得正确的代码应该是这样的:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

import org.apache.commons.dbutils.DbUtils;

public class MyJdbcUitls {
public int updateData(Connection con, String sql, List<Object> paramValueList) throws SQLException {
// int resultRow = 0; try{
// Connection con = ...
// statement = con.createStatement();
// resultRow = statement.executeUpdate(sql);
// ... } catch (SQLException e) {
// e.printStackTrace(); }
// return resultRow; }}
PreparedStatement ps = null;
try {
ps = con.prepareStatement(sql);
if (paramValueList != null) {
for (int i = 0; i < paramValueList.size(); i++) {
setOneParameter(i, ps, paramValueList.get(i));
}
}

int count = ps.executeUpdate();
return count;
} finally {
DbUtils.closeQuietly(ps);
}
}
}


注意:

之所以要把 connection 从外面传入,因为写这个 update 的函数时,还不能确定,实际业务逻辑,是一个 update 函数就是一个 transaction,还是多个 update/delete 组合在一起,做一个 transaction。

 

补充:

数据库事务控制,应该从数据库访问层中独立出来,这里是比较正确的控制流程:

用户点击 -- 数据库事务控制层 --- 调用一个或者多个数据访问层函数 ---- 代码返回到数据库事务控制层,决定 commit/rollback。

 

这样做的原因在于:无法避免用户在代码中连续调用多个数据访问层函数,如果在每个数据访问层函数中,commit/rollback,会造成整个操作有多个数据库事务,以下是错误的流程:

用户点击 --  调用一个或者多个数据访问层函数(每个函数中有 commit/rollback)。

 

可以写一个这样类 JdbcTransactionUtils, 其中包含的函数:

public static void doWithJdbcTransactionDefaultCommit(SqlRunnable run, Connection con) {
doWithJdbcTransactionNoCommitRollback(run, con);
try {
con.commit();
} catch (Exception e) {
Log log = LogFactory.getLog(JdbcTransactionUtils.class);
log.error(e.getMessage(), e);
try {
con.rollback();
} catch (Exception err) {
log.error(err.getMessage(), err);
}
throw new NestableRuntimeException(e.getMessage(), e);
}
}


要避免把 commit/rollback 做成公共函数,因为那样,其他程序员一不小心漏掉了什么,就有问题了。写公共函数,要做到易用、不易被错用。

上面的数据库事务控制函数可以做到。

然而,这样还不算完美。毕竟,马虎的程序员,还是可以在一个 click 中调用多个[b]数据库事务控制层,也就是调用多个 [b][b]JdbcTransactionUtils.[/b][/b]doWithJdbcTransactionDefaultCommit(), 结果如下:[/b]

 

用户点击 -- 数据库事务控制层函数1 --- 调用一个或者多个数据访问层函数 ---- 代码返回到数据库事务控制层,决定 commit/rollback --[b][b]数据库事务控制层函数2 --- 调用一个或者多个数据访问层函数 ---- 代码返回到数据库事务控制层,决定 commit/rollback[/b]。[/b]

还是不好。

 

实际上,我们期望的是,每次用户点击,后台都应该是一个数据库 transaction,因此,我的意思是,数据库事务控制代码,要和 web 层的后台处理代码(比如 struts 的 action ,  asp.net 页面对应的 .cs 文件),合并掉,并在此处理 try catch。至于其他被调用的函数,比如数据库访问函数,比如工具类,都不要 try catch。毕竟,数据库访问函数,比如工具类,都可能被多个地方的代码调用,如果在里面写
try catch, 如何写 try catch 达到所有调用的模块都满意,是很难做到的。

 

最后我认为合理的流程如下:

用户点击 -- 用户点击处理程序(struts action/asp.net 页面.cs),包含 try catch,包含数据库事务控制 --- 调用一个或者多个数据访问层函数(无 try catch) --- 调用一个或者多个工具类函数(无 try catch)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: