Spring JDBC пул соединений и результаты InputStream - PullRequest
4 голосов
/ 07 августа 2009

Я пишу веб-сервис, который позволяет пользователям публиковать файлы, а затем извлекать их по URL-адресу (в основном это RESTful Amazon S3). Проблема, с которой я столкнулся, заключалась в том, что вместо того, чтобы вернуть байт [] из моего запроса Oracle (Spring JDBC), я возвращаю InputStream и затем передаю данные обратно клиенту порциями. Это (IMO) гораздо лучшая идея, так как я не ограничиваю размер файла и не хочу, чтобы в памяти были 2-байтовые массивы.

Сначала казалось, что все работает нормально, но во время большой нагрузки я столкнулся с случаем, что иногда Соединение может быть повторно использовано до того, как предыдущий сервлет сможет отправить файл. Кажется, что после вызова JDBC, который вернул InputStream, Connection будет возвращен в пул (Spring вызовет conn.close (), но не очистит связанный ResultSet). Таким образом, если не было получено никакого другого запроса на это Соединение, тогда InputStream все еще будет действительным и его можно будет прочитать, но если Соединение было передано новому запросу, тогда InputStream будет нулевым, а предыдущий запрос завершится неудачей.

Мое решение состояло в том, чтобы создать подкласс InputStream, который также принимает Connection в качестве аргумента конструктора, а в переопределенном методе 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
         }
      }
   }
} 

У кого-нибудь есть более элегантное решение, или вы видите какие-либо явные проблемы с моим решением? Также этот код не был вырезан / вставлен из моего реального кода, поэтому, если есть опечатка, просто проигнорируйте ее.

Ответы [ 3 ]

3 голосов
/ 08 августа 2009

К сожалению, мое воображение иссякло, когда вы задали этот вопрос. Я не знаю, считается ли это решение более элегантным. Тем не менее, эти классы просты и легко используются повторно, поэтому вы можете найти их применение, если они не являются удовлетворительными. В конце вы увидите, что все собирается вместе ...

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 метод *1031*, это именно то, что происходит (за исключением обработки исключений для ясности):

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 объектов, сколько хотите.

0 голосов
/ 07 января 2018

Альтернативный подход заключается в использовании обратного вызова. Ниже вид идеи.

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
   }
}
0 голосов
/ 07 августа 2009

Почему бы не прочитать весь InputStream / byte[] / что-либо из запроса перед его самостоятельным выпуском? Похоже, вы пытаетесь вернуть данные из запроса после того, как ваш код сообщил Spring / пулу, что вы закончили с подключением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...