代码之家  ›  专栏  ›  技术社区  ›  Gandalf

Spring JDBC连接池和输入流结果

  •  4
  • Gandalf  · 技术社区  · 15 年前

    我正在编写一个WebService,它允许用户发布文件,然后在一个URL上检索它们(基本上把它看作是一个RESTfulAmazonS3)。我遇到的问题是,我宁可从我的Oracle查询(SpringJDBC)返回一个byte[],我要返回一个inputstream,然后将数据以块形式流回到客户机。这个(imo)是一个更好的主意,因为我对文件没有大小限制,也不希望在内存中使用2GB字节数组。

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

    我的解决方案是创建inputstream的子类,该子类还将连接作为构造函数参数,并在重写的 公共闭包() 方法还关闭连接。我不得不放弃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 回复  |  直到 6 年前
        1
  •  3
  •   Adam Paynter    15 年前

    不幸的是,当你问这个问题时,我的想象力变得疯狂了。我不知道这个解决方案是否被认为更优雅。但是,这些类很简单,并且很容易重用,因此如果它们不令人满意,您可能会发现它们的用途。你会看到一切都在最后…

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

    你现在可以自由关闭 Closeable 你喜欢的东西。

        2
  •  0
  •   matt b    15 年前

    为什么不通读 InputStream / byte[] /在自己释放查询之前从查询中得到什么?在代码告诉Spring/池您已经完成了连接之后,您似乎正在尝试从查询返回数据。

        3
  •  0
  •   David Bradley    6 年前

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

    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
       }
    }