我正在编写一个Web服务,允许用户发布文件,然后在URL上检索它们(基本上将其视为RESTful Amazon S3)。我遇到的问题是从我的Oracle查询返回一个byte [](Spring JDBC)我返回一个InputStream,然后以块的形式将数据流回客户端。这个(IMO)是一个更好的主意,因为我对文件没有任何大小限制,我不希望内存中有2GB字节数组。
起初它看起来工作正常,但是我在重负载期间遇到了一个案例,有时在上一个servlet可以发送文件之前,Connection会被重用。似乎在返回InputStream的JDBC调用之后,Connection将返回到池(Spring将调用conn.close(),但不清除关联的ResultSet)。因此,如果没有给出Connection的其他请求,则InputStream仍然有效并且可以从中读取,但是如果Connection被赋予新请求,那么InputStream将为null并且先前的请求将失败。
我的解决方案是创建一个InputStream的子类,它也将Connection作为构造函数arg,并且在重写的 public close()方法中也关闭Connection。我不得不抛弃Spring JDBC并只进行正常的PreparedStatement调用,否则Spring总会返回到池的连接。
public class ConnectionInputStream extends InputStream {
private Connection conn;
private InputStream stream;
public ConnectionInputStream(InputStream s, Connection c) {
conn = c;
stream = s;
}
// all InputStream methods call the same method on the variable stream
@Override
public void close() throws IOException {
try {
stream.close();
} catch (IOException ioex) {
//do something
} finally {
try {
conn.close();
} catch (SQLException sqlex) {
//ignore
}
}
}
}
有没有人有更优雅的解决方案,或者看到我的解决方案有任何明显的问题?此代码也没有从我的实际代码中剪切/粘贴,所以如果有拼写错误就忽略它。
答案 0 :(得分:3)
不幸的是,当你问这个问题时,我的想象力很疯狂。我不知道这个解决方案是否更优雅。但是,这些类很简单且易于重复使用,因此如果它们不令人满意,您可能会发现它们的用途。你会看到最后一切都在一起......
public class BinaryCloseable implements Closeable {
private Closeable first;
private Closeable last;
public BinaryCloseable(Closeable first, Closeable last) {
this.first = first;
this.last = last;
}
@Override
public void close() throws IOException {
try {
first.close();
} finally {
last.close();
}
}
}
BinaryCloseable
使用 CompositeCloseable
:
public class CompositeCloseable implements Closeable {
private Closeable target;
public CompositeCloseable(Closeable... closeables) {
target = new Closeable() { public void close(){} };
for (Closeable closeable : closeables) {
target = new BinaryCloseable(target, closeable);
}
}
@Override
public void close() throws IOException {
target.close();
}
}
ResultSetCloser
关闭ResultSet
个对象:
public class ResultSetCloser implements Closeable {
private ResultSet resultSet;
public ResultSetCloser(ResultSet resultSet) {
this.resultSet = resultSet;
}
@Override
public void close() throws IOException {
try {
resultSet.close();
} catch (SQLException e) {
throw new IOException("Exception encountered while closing result set", e);
}
}
}
PreparedStatementCloser
关闭PreparedStatement
个对象:
public class PreparedStatementCloser implements Closeable {
private PreparedStatement preparedStatement;
public PreparedStatementCloser(PreparedStatement preparedStatement) {
this.preparedStatement = preparedStatement;
}
@Override
public void close() throws IOException {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new IOException("Exception encountered while closing prepared statement", e);
}
}
}
ConnectionCloser
关闭Connection
个对象:
public class ConnectionCloser implements Closeable {
private Connection connection;
public ConnectionCloser(Connection connection) {
this.connection = connection;
}
@Override
public void close() throws IOException {
try {
connection.close();
} catch (SQLException e) {
throw new IOException("Exception encountered while closing connection", e);
}
}
}
我们现在将您原来的InputStream
想法重构为:
public class ClosingInputStream extends InputStream {
private InputStream stream;
private Closeable closer;
public ClosingInputStream(InputStream stream, Closeable closer) {
this.stream = stream;
this.closer = closer;
}
// The other InputStream methods...
@Override
public void close() throws IOException {
closer.close();
}
}
最后,它们汇集在一起:
new ClosingInputStream(
stream,
new CompositeCloseable(
stream,
new ResultSetCloser(resultSet),
new PreparedStatementCloser(statement),
new ConnectionCloser(connection)
)
);
当调用此ClosingInputStream
的{{1}}方法时,实际上会发生这种情况(为清楚起见省略了异常处理):
close()
您现在可以随意关闭尽可能多的public void close() {
try {
try {
try {
try {
// This is empty due to the first line in `CompositeCloseable`'s constructor
} finally {
stream.close();
}
} finally {
resultSet.close();
}
} finally {
preparedStatement.close();
}
} finally {
connection.close();
}
}
个对象。
答案 1 :(得分:0)
在自己发布查询之前,为什么不从查询中读取整个InputStream
/ byte[]
/其他内容?在您的代码告诉Spring /池完成连接后,听起来您正试图从查询中返回数据。
答案 2 :(得分:0)
另一种方法是使用回调。以下是这个想法。
class MyDao
{
public boolean getData(Function<InputStream, Boolean> processData) {
// Do your SQL stuff to get a ResultSet
InputStream input = resultSet.getBinaryStream(0);
processData.apply(input);
// Do your cleanup if any
}
}