有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

JavaSpringJDBC连接池和InputStream结果

我正在编写一个Web服务,它允许用户发布文件,然后通过URL检索它们(基本上可以将其视为RESTfulAmazonS3)。我遇到的问题不是从Oracle查询(Spring JDBC)返回一个字节[],而是返回一个InputStream,然后将数据分块流回到客户机。这(IMO)是一个更好的主意,因为我对文件没有大小限制,并且我不希望内存中有2GB字节数组

起初,它似乎工作得很好,但我在重载期间遇到了这样一种情况:有时在前一个servlet发送文件之前,连接会被重用。似乎在返回InputStream的JDBC调用之后,连接将返回到池(Spring将调用conn.close(),但不清除关联的ResultSet)。因此,如果没有为该连接提供其他请求,那么InputStream仍然有效并且可以从中读取,但是如果为新请求提供了连接,那么InputStream将为null,并且之前的请求将失败

我的解决方案是创建InputStream的子类,该子类也将连接作为构造函数arg,并且在重写的public close()方法中也关闭连接。我不得不放弃SpringJDBC,只做一个普通的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
         }
      }
   }
} 

有没有人有更优雅的解决方案,或者看到我的解决方案有什么突出的问题?此外,这段代码不是从我的实际代码中剪切/粘贴的,所以如果有输入错误,请忽略它


共 (3) 个答案

  1. # 1 楼答案

    在自己发布查询之前,为什么不从查询中读取整个InputStream/byte[]/任何内容?这听起来像是在代码告诉Spring/池您已经完成了连接之后,您正在尝试从查询返回数据

  2. # 2 楼答案

    另一种方法是使用回调。下面是一种想法

    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
       }
    }
    
  3. # 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();
            }
        }
    
    }
    

    BinaryCloseableCompositeCloseable使用:

    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)
                )
        );
    

    当调用这个ClosingInputStreamclose()方法时,实际上会发生这种情况(为了清楚起见,省略了异常处理):

    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();
        }
    }
    

    您现在可以随意关闭任意多个Closeable对象