Повторное использование PreparedStatement при использовании драйвера Datastax Cassandra?

В настоящее время я использую драйвер Datastax Cassandra для Cassandra 2 для выполнения cql3. Это работает правильно. Я начал использовать PreparedStatement's:

Session session = sessionProvider.getSession();
try {
    PreparedStatement ps = session.prepare(cql);
    ResultSet rs = session.execute(ps.bind(objects));
    if (irsr != null) {
       irsr.read(rs);
    }
}

Иногда я получаю предупреждение от драйвера в моем журнале:

Re-preparing already prepared query . Please note that preparing the same query more than once is generally an anti-pattern and will likely affect performance. Consider preparing the statement only once.

Это предупреждение имеет смысл, но я не уверен, как я должен повторно использовать PreparedStatement?

Должен ли я просто создать все мои PreparedStatement в методе конструктора /init и просто использовать их?

Но это хорошо, когда несколько потоков используют один и тот же PreparedStatement в одно и то же время (особенно вызов PreparedStatement.bind() для привязки объектов)

Ответ 1

Вы можете просто инициализировать PreparedStatement один раз и кэшировать его во время работы приложения. Он должен быть доступен для использования до тех пор, пока кластер Cassandra не работает.

Использование инструкции из нескольких потоков является точной (если вы не изменяете ее с помощью методов setXXX()). Когда вы вызываете bind(), код под ним только считывает PreparedStatement, а затем создает новый экземпляр BoundStatement(), который затем поток вызывающего пользователя может мутировать.

Вот исходный код, если вам интересно (поиск bind()).

Ответ 2

Мы используем cassandra в веб-приложении с Spring. В нашем случае мы создаем PreparedStatements, когда bean, который инкапсулирует операцию против cf (наш репозиторий), устанавливается.

Здесь у вас есть фрагмент кода, который мы используем:

@Repository
public class StatsRepositoryImpl implements StatsRepository {

@SuppressWarnings("unused")
    @PostConstruct
    private void initStatements(){
        if (cassandraSession == null){
            LOG.error("Cassandra 2.0 not available");
        } else {
            GETSTATS_BY_PROJECT = cassandraSession.prepare(SELECTSTATS+" WHERE projectid = ?");
        }

    }       

@Override
    public Stats findByProject(Project project) {
        Stats stats = null;

        BoundStatement boundStatement = new BoundStatement(GETSTATS_BY_PROJECT);

        ResultSet rs = cassandraSession.execute(boundStatement.bind(project.getId()));
        for (Row row : rs){
            stats = mapRowToStats(row);
        }

        return stats;
    } 

Таким образом, подготовленные операторы повторно используются каждый раз, когда мы выполняем метод findByProject.

Ответ 3

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

Проверяйте пространство ключей из подготовленного оператора, если он такой же, как и переданный аргумент, тогда не готовят оператор, поскольку он уже подготовлен в этом случае.

 private BatchStatement eventBatch(List<SomeEvent> events, String keySpace) {

    BatchStatement batch = new BatchStatement();
    String preparedStmtKeySpace = propEventPer == null? "" : propEventPer.getQueryKeyspace();
    if(!keySpace.equals("\"" + preparedStmtKeySpace +  "\"")) {
        eventStmt = cassandraOperations.getSession().prepare(colFamilyInsert(keySpace + "." + "table_name"));

    }
    ....
private RegularStatement colFamilyInsert(String colFamilyName) {
    return insertInto(colFamilyName)
            .value(PERSON_ID, bindMarker())
            .value(DAY, bindMarker());

}

Ответ 4

03-Apr-2017 10:02:24,120 WARN  [com.datastax.driver.core.Cluster] (cluster1-worker-2851) Re-preparing already prepared query is generally an anti-pattern and will likely affect performance. Consider preparing the statement only once. Query='select * from xxxx where cjpid=? and cjpsnapshotid =?'

Создайте пул объектов PreparedStatement, по одному для каждого запроса CQL.

Затем, когда эти запросы запрашиваются клиентом, выберите соответствующий кешированный объект PS и укажите значения, вызвав bind().

как объяснил Даниэль, bind() делает new BoundStmt(param), что делает этот поток безопасным.