我正在使用Spring JDBCTemboard访问数据库中的数据,它工作正常。但是FindBugs在我的代码片段中指出了一个小问题。
代码:
public String createUser(final User user) {
try {
final String insertQuery = "insert into user (id, username, firstname, lastname) values (?, ?, ?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" });
ps.setInt(1, user.getUserId());
ps.setString(2, user.getUserName());
ps.setString(3, user.getFirstName());
ps.setInt(4, user.getLastName());
return ps;
}
}, keyHolder);
int userId = keyHolder.getKey().intValue();
return "user created successfully with user id: " + userId;
} catch (DataAccessException e) {
log.error(e, e);
}
}
FindBugs问题:
方法可能无法清理流或资源的检查异常在这一行准备语句ps=connection.prepare语句(插入查询, new String[]{"id" });
有人能告诉我这到底是什么吗?我们如何解决这个问题?
我们将不胜感激:)
PreparedStatement
是一个可关闭的资源。然而,看起来JDBC模板负责关闭它——因此FindBugs可能偶然发现了一个假阳性。
是的,这看起来像是一个假阳性,FindBugs团队希望听到这个假阳性,这样他们可以调整这个警告。他们在其他测试中为第三方方法添加了特定的异常,我希望这将以同样的方式处理。您可以提交错误报告或向团队发送电子邮件。
但是,现在,您可以使用SubpressFBWarning
注释在这种情况下忽略此警告:
@SuppressFBWarnings("OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE")
public PreparedStatement createPreparedStatement...
为了提高易读性并允许重用警告,我发现在帮助类中定义常量很有帮助:
public final class FindBugs {
final String UNCLOSED_RESOURCE = "OBL_UNSATISFIED_OBLIGATION_EXCEPTION_EDGE";
private FindBugs() {
// static only
}
}
...
@SuppressFBWarnings(FindBugs.UNCLOSED_RESOURCE)
不幸的是,我无法定义忽略特定警告的注释。
FindBugs关于异常情况下潜在泄漏的说法是正确的,因为setInt和setString被声明为抛出“SQLException”。如果这些行中有任何一行抛出SQLException,那么PreparedStatement就会泄漏,因为没有可以关闭它的作用域块。
为了更好地理解这个问题,让我们打破代码错觉,摆脱spring类型,以一种近似调用返回资源的方法时调用堆栈作用域的方式内联方法。
public void leakyMethod(Connection con) throws SQLException {
PreparedStatement notAssignedOnThrow = null; //Simulate calling method storing the returned value.
try { //Start of what would be createPreparedStatement method
PreparedStatement inMethod = con.prepareStatement("select * from foo where key = ?");
//If we made it here a resource was allocated.
inMethod.setString(1, "foo"); //<--- This can throw which will skip next line.
notAssignedOnThrow = inMethod; //return from createPreparedStatement method call.
} finally {
if (notAssignedOnThrow != null) { //No way to close because it never
notAssignedOnThrow.close(); //made it out of the try block statement.
}
}
}
回到最初的问题,如果user
为null,由于没有用户给定或其他一些自定义异常(比如UserNotLoggedInException
从getUserId()
的深层抛出),导致NullPointerException
也是如此。
下面是一个解决此问题的丑陋修复示例:
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
boolean fail = true;
PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" });
try {
ps.setInt(1, user.getUserId());
ps.setString(2, user.getUserName());
ps.setString(3, user.getFirstName());
ps.setInt(4, user.getLastName());
fail = false;
} finally {
if (fail) {
try {
ps.close();
} catch(SQLException warn) {
}
}
}
return ps;
}
因此,在本例中,它仅在出现问题时关闭语句。否则返回一个打开的语句供调用者清理。最后一个块用于捕获块,因为错误驱动程序实现可以抛出不仅仅是SQLException对象。不使用捕获块和重新抛出,因为在极少数情况下检查可抛出的类型可能会失败。
在JDK 7和JDK 8中,您可以编写如下修补程序:
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" });
try {
ps.setInt(1, user.getUserId());
ps.setString(2, user.getUserName());
ps.setString(3, user.getFirstName());
ps.setInt(4, user.getLastName());
} catch (Throwable t) {
try {
ps.close();
} catch (SQLException warn) {
if (t != warn) {
t.addSuppressed(warn);
}
}
throw t;
}
return ps;
}
在JDK 9及更高版本中,您可以编写如下修补程序:
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" });
try {
ps.setInt(1, user.getUserId());
ps.setString(2, user.getUserName());
ps.setString(3, user.getFirstName());
ps.setInt(4, user.getLastName());
} catch (Throwable t) {
try (ps) { // closes statement on error
throw t;
}
}
return ps;
}
关于Spring,请告诉你的用户。getUserId()
方法可能会引发IllegalStateException,或者给定的用户是null
。根据合同规定,Spring不指定如果java。lang.RuntimeException或java。lang.错误从PreparedStatementCreator抛出。根据文件:
实现不需要担心它们尝试的操作可能引发的SQLException。JdbcTemplate类将捕获并适当处理SQLException。
这种措辞暗示Spring依赖connection.close()来完成这项工作。
让我们进行概念验证,以验证Spring文档promise的内容。
public class LeakByStackPop {
public static void main(String[] args) throws Exception {
Connection con = new Connection();
try {
PreparedStatement ps = createPreparedStatement(con);
try {
} finally {
ps.close();
}
} finally {
con.close();
}
}
static PreparedStatement createPreparedStatement(Connection connection) throws Exception {
PreparedStatement ps = connection.prepareStatement();
ps.setXXX(1, ""); //<---- Leak.
return ps;
}
private static class Connection {
private final PreparedStatement hidden = new PreparedStatement();
Connection() {
}
public PreparedStatement prepareStatement() {
return hidden;
}
public void close() throws Exception {
hidden.closeFromConnection();
}
}
private static class PreparedStatement {
public void setXXX(int i, String value) throws Exception {
throw new Exception();
}
public void close() {
System.out.println("Closed the statement.");
}
public void closeFromConnection() {
System.out.println("Connection closed the statement.");
}
}
}
由此产生的输出是:
Connection closed the statement.
Exception in thread "main" java.lang.Exception
at LeakByStackPop$PreparedStatement.setXXX(LeakByStackPop.java:52)
at LeakByStackPop.createPreparedStatement(LeakByStackPop.java:28)
at LeakByStackPop.main(LeakByStackPop.java:15)
如您所见,连接是对准备好的语句的唯一引用。
让我们更新这个示例,通过修补伪“PreparedStatementCreator”方法修复内存泄漏。
public class LeakByStackPop {
public static void main(String[] args) throws Exception {
Connection con = new Connection();
try {
PreparedStatement ps = createPreparedStatement(con);
try {
} finally {
ps.close();
}
} finally {
con.close();
}
}
static PreparedStatement createPreparedStatement(Connection connection) throws Exception {
PreparedStatement ps = connection.prepareStatement();
try {
//If user.getUserId() could throw IllegalStateException
//when the user is not logged in then the same leak can occur.
ps.setXXX(1, "");
} catch (Throwable t) {
try {
ps.close();
} catch (Exception suppressed) {
if (suppressed != t) {
t.addSuppressed(suppressed);
}
}
throw t;
}
return ps;
}
private static class Connection {
private final PreparedStatement hidden = new PreparedStatement();
Connection() {
}
public PreparedStatement prepareStatement() {
return hidden;
}
public void close() throws Exception {
hidden.closeFromConnection();
}
}
private static class PreparedStatement {
public void setXXX(int i, String value) throws Exception {
throw new Exception();
}
public void close() {
System.out.println("Closed the statement.");
}
public void closeFromConnection() {
System.out.println("Connection closed the statement.");
}
}
}
由此产生的输出是:
Closed the statement.
Exception in thread "main" java.lang.Exception
Connection closed the statement.
at LeakByStackPop$PreparedStatement.setXXX(LeakByStackPop.java:63)
at LeakByStackPop.createPreparedStatement(LeakByStackPop.java:29)
at LeakByStackPop.main(LeakByStackPop.java:15)
正如您所看到的,每个分配都是平衡的,以释放资源。
它显示了这个特定声明的问题。你能帮帮我吗?
我代码中的以下语句被SonarQube标记为一个关键问题。stmt=conn.createStatement(); 确切的问题是,“方法可能无法清理java.sql.检查异常的语句”。 下面是代码片段: SonarQube给出的规则如下: 方法可能无法在检查异常时清理流或资源此方法可能无法清理(关闭、处置)需要显式清理操作的流、数据库对象或其他资源。一般情况下,如果一个方法打开一个流或其他资源,该
我正在使用FileWrite类写入文件。而且效果很好。但是FindBugs在我的代码片段中指出了一个小问题。 代码片段: Findbug报告: OBL_未满足_义务:方法可能无法清理流,或者资源WriteDataFile(字符串)可能无法清理java。伊奥。已检查异常的写入程序 我在哪一行遇到这个错误? 有人能告诉我这到底是什么吗?我们如何解决这个问题?
本文向大家介绍如何快速清理 docker 资源的方法,包括了如何快速清理 docker 资源的方法的使用技巧和注意事项,需要的朋友参考一下 如果经常使用 docker,你会发现 docker 占用的资源膨胀很快,其中最明显也最容易被察觉的应该是对磁盘空间的占用。本文将介绍如何快速的清理 docker 占用的系统资源,具体点说就是删除那些无用的 镜像、容器、网络和数据卷。 查看 docker 占用的
问题内容: 在Java线程中,“运行”方法不能引发“检查的异常”。我在Core Java(第1卷)书中遇到了这个问题。有人可以解释其背后的原因吗? 问题答案: 有人可以解释其背后的原因吗? 是的,因为您抛出的任何异常都会被JVM仔细忽略。因此,将其抛出可能是一个错误(除非您对该线程具有特定的异常处理程序,请参阅有关该文档的文档)。没有理由煽动潜在的错误行为。 或者举个例子。 编辑 为什么父线程不能
我正试图修改PySpark dataframe中的列值,如下所示: 这将生成以下异常: 调用O435时出错。跟踪:py4j.py4jException:Method或([class java.lang.string])在py4j.reflection.reflectionEngine.getMethod(reflectionEngine.java:318)在py4j.reflection.refl