Unk*_*own 7 java spring findbugs jdbctemplate
我正在使用Spring JDBCTemplate访问数据库中的数据,并且其工作正常.但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);
}
}
Run Code Online (Sandbox Code Playgroud)
FindBugs问题:
方法可能无法清除此行中已检查异常的流或资源 PreparedStatement ps = connection.prepareStatement(insertQuery, new String[] { "id" });
有人可以告诉我这究竟是什么?我们如何解决这个问题?
帮助将不胜感激:)
FindBugs 关于异常情况下的潜在泄漏是正确的,因为setInt和setString被声明为抛出'SQLException'.如果这些行中的任何一行抛出SQLException,则PreparedStatement会泄露,因为没有可以关闭它的范围块.
为了更好地理解这个问题,让我们通过去掉spring类型并内联方法来分解代码错觉,这种方法是调用返回资源的方法时callstack作用域的工作方式的近似值.
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.
}
}
}
Run Code Online (Sandbox Code Playgroud)
回到最初的问题,如果user
是null导致NullPointerException
由于没有给定用户或者UserNotLoggedInException
从内部深处抛出一些其他自定义异常,则同样如此getUserId()
.
以下是此问题的丑陋修复示例:
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;
}
Run Code Online (Sandbox Code Playgroud)
所以在这个例子中,只有在出现问题时才会关闭语句.否则返回一个打开的声明供调用者清理.在catch块上使用finally块,因为错误的驱动程序实现可以抛出的不仅仅是SQLException对象.不使用捕获块和重新抛出,因为在极少数情况下检查throwable的类型可能会失败.
在JDK 7及更高版本中,您可以像这样编写补丁:
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;
}
Run Code Online (Sandbox Code Playgroud)
关于Spring,假设您的user.getUserId()
方法可能抛出IllegalStateException或给定的用户null
.在契约方面,Spring没有指定如果从PreparedStatementCreator 抛出java.lang.RuntimeException或java.lang.Error会发生什么.根据文档:
实现不需要关心可能从它们尝试的操作抛出的SQLExceptions.JdbcTemplate类将适当地捕获和处理SQLExceptions.
这个措辞意味着Spring依赖于connection.close()来完成这项工作.
让我们进行概念验证,以验证Spring文档所承诺的内容.
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;
}
Run Code Online (Sandbox Code Playgroud)
结果输出是:
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.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
如您所见,连接是对预准备语句的唯一引用.
让我们通过修补伪造的'PreparedStatementCreator'方法更新示例以修复内存泄漏.
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)
Run Code Online (Sandbox Code Playgroud)
结果输出是:
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.");
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,每个分配都是平衡的,并且接近释放资源.
归档时间: |
|
查看次数: |
11524 次 |
最近记录: |