All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.cassandra.core.CassandraTemplate Maven / Gradle / Ivy

There is a newer version: 4.3.2
Show newest version
/*
 * Copyright 2016-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.cassandra.core;

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.data.cassandra.SessionFactory;
import org.springframework.data.cassandra.core.EntityOperations.AdaptibleEntity;
import org.springframework.data.cassandra.core.convert.CassandraConverter;
import org.springframework.data.cassandra.core.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.core.convert.QueryMapper;
import org.springframework.data.cassandra.core.convert.UpdateMapper;
import org.springframework.data.cassandra.core.cql.CassandraAccessor;
import org.springframework.data.cassandra.core.cql.CqlOperations;
import org.springframework.data.cassandra.core.cql.CqlProvider;
import org.springframework.data.cassandra.core.cql.CqlTemplate;
import org.springframework.data.cassandra.core.cql.PreparedStatementBinder;
import org.springframework.data.cassandra.core.cql.PreparedStatementCreator;
import org.springframework.data.cassandra.core.cql.QueryExtractorDelegate;
import org.springframework.data.cassandra.core.cql.QueryOptions;
import org.springframework.data.cassandra.core.cql.RowMapper;
import org.springframework.data.cassandra.core.cql.SessionCallback;
import org.springframework.data.cassandra.core.cql.SingleColumnRowMapper;
import org.springframework.data.cassandra.core.cql.WriteOptions;
import org.springframework.data.cassandra.core.cql.session.DefaultSessionFactory;
import org.springframework.data.cassandra.core.cql.util.StatementBuilder;
import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity;
import org.springframework.data.cassandra.core.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.core.mapping.event.AfterConvertEvent;
import org.springframework.data.cassandra.core.mapping.event.AfterDeleteEvent;
import org.springframework.data.cassandra.core.mapping.event.AfterLoadEvent;
import org.springframework.data.cassandra.core.mapping.event.AfterSaveEvent;
import org.springframework.data.cassandra.core.mapping.event.BeforeConvertCallback;
import org.springframework.data.cassandra.core.mapping.event.BeforeDeleteEvent;
import org.springframework.data.cassandra.core.mapping.event.BeforeSaveCallback;
import org.springframework.data.cassandra.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.cassandra.core.mapping.event.CassandraMappingEvent;
import org.springframework.data.cassandra.core.query.Columns;
import org.springframework.data.cassandra.core.query.Query;
import org.springframework.data.domain.Slice;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.projection.EntityProjection;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.CqlSession;
import com.datastax.oss.driver.api.core.DriverException;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.cql.BatchType;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.SimpleStatement;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import com.datastax.oss.driver.api.querybuilder.delete.Delete;
import com.datastax.oss.driver.api.querybuilder.insert.Insert;
import com.datastax.oss.driver.api.querybuilder.insert.RegularInsert;
import com.datastax.oss.driver.api.querybuilder.select.Select;
import com.datastax.oss.driver.api.querybuilder.truncate.Truncate;
import com.datastax.oss.driver.api.querybuilder.update.Update;

/**
 * Primary implementation of {@link CassandraOperations}. It simplifies the use of Cassandra usage and helps to avoid
 * common errors. It executes core Cassandra workflow. This class executes CQL queries or updates, initiating iteration
 * over {@link ResultSet} and catching Cassandra exceptions and translating them to the generic, more informative
 * exception hierarchy defined in the {@code org.springframework.dao} package.
 * 

* Can be used within a service implementation via direct instantiation with a {@link CqlSession} reference, or get * prepared in an application context and given to services as bean reference. *

* This class supports the use of prepared statements when enabling {@link #setUsePreparedStatements(boolean)}. All * statements created by methods of this class (such as {@link #select(Query, Class)} or * {@link #update(Query, org.springframework.data.cassandra.core.query.Update, Class)} will be executed as prepared * statements. Also, statements accepted by methods (such as {@link #select(String, Class)} or * {@link #select(Statement, Class) and others}) will be prepared prior to execution. Note that {@link Statement} * objects passed to methods must be {@link SimpleStatement} so that these can be prepared. *

* Note: The {@link CqlSession} should always be configured as a bean in the application context, in the first case * given to the service directly, in the second case to the prepared template. * * @author Mark Paluch * @author John Blum * @author Lukasz Antoniak * @author Sam Lightfoot * @see org.springframework.data.cassandra.core.CassandraOperations * @since 2.0 */ public class CassandraTemplate implements CassandraOperations, ApplicationEventPublisherAware, ApplicationContextAware { private final Log log = LogFactory.getLog(getClass()); private final CqlOperations cqlOperations; private final CassandraConverter converter; private final EntityOperations entityOperations; private final StatementFactory statementFactory; private final EntityLifecycleEventDelegate eventDelegate; private @Nullable EntityCallbacks entityCallbacks; private boolean usePreparedStatements = true; /** * Creates an instance of {@link CassandraTemplate} initialized with the given {@link CqlSession} and a default * {@link MappingCassandraConverter}. * * @param session {@link CqlSession} used to interact with Cassandra; must not be {@literal null}. * @see CassandraConverter * @see CqlSession */ public CassandraTemplate(CqlSession session) { this(session, newConverter(session)); } /** * Creates an instance of {@link CassandraTemplate} initialized with the given {@link CqlSession} and * {@link CassandraConverter}. * * @param session {@link CqlSession} used to interact with Cassandra; must not be {@literal null}. * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be * {@literal null}. * @see CassandraConverter * @see CqlSession */ public CassandraTemplate(CqlSession session, CassandraConverter converter) { this(new DefaultSessionFactory(session), converter); } /** * Creates an instance of {@link CassandraTemplate} initialized with the given {@link SessionFactory} and * {@link CassandraConverter}. * * @param sessionFactory {@link SessionFactory} used to interact with Cassandra; must not be {@literal null}. * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be * {@literal null}. * @see CassandraConverter * @see SessionFactory */ public CassandraTemplate(SessionFactory sessionFactory, CassandraConverter converter) { this(new CqlTemplate(sessionFactory), converter); } /** * Creates an instance of {@link CassandraTemplate} initialized with the given {@link CqlOperations} and * {@link CassandraConverter}. * * @param cqlOperations {@link CqlOperations} used to interact with Cassandra; must not be {@literal null}. * @param converter {@link CassandraConverter} used to convert between Java and Cassandra types; must not be * {@literal null}. * @see CassandraConverter * @see CqlSession */ public CassandraTemplate(CqlOperations cqlOperations, CassandraConverter converter) { Assert.notNull(cqlOperations, "CqlOperations must not be null"); Assert.notNull(converter, "CassandraConverter must not be null"); this.converter = converter; this.cqlOperations = cqlOperations; this.entityOperations = new EntityOperations(converter); this.statementFactory = new StatementFactory(new QueryMapper(converter), new UpdateMapper(converter)); this.eventDelegate = new EntityLifecycleEventDelegate(); } @Override public CassandraBatchOperations batchOps(BatchType batchType) { return new CassandraBatchTemplate(this, batchType); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventDelegate.setPublisher(applicationEventPublisher); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (entityCallbacks == null) { setEntityCallbacks(EntityCallbacks.create(applicationContext)); } } /** * Configure {@link EntityCallbacks} to pre-/post-process entities during persistence operations. * * @param entityCallbacks */ public void setEntityCallbacks(@Nullable EntityCallbacks entityCallbacks) { this.entityCallbacks = entityCallbacks; } /** * Configure whether lifecycle events such as {@link AfterLoadEvent}, {@link BeforeSaveEvent}, etc. should be * published or whether emission should be suppressed. Enabled by default. * * @param enabled {@code true} to enable entity lifecycle events; {@code false} to disable entity lifecycle events. * @since 4.0 * @see CassandraMappingEvent */ public void setEntityLifecycleEventsEnabled(boolean enabled) { this.eventDelegate.setEventsEnabled(enabled); } @Override public CqlOperations getCqlOperations() { return this.cqlOperations; } @Override public CassandraConverter getConverter() { return this.converter; } /** * Returns whether this instance is configured to use {@link PreparedStatement prepared statements}. If enabled * (default), then all persistence methods (such as {@link #select}, {@link #update}, and others) will make use of * prepared statements. Note that methods accepting a {@link Statement} must be called with {@link SimpleStatement} * instances to participate in statement preparation. * * @return {@literal true} if prepared statements usage is enabled; {@literal false} otherwise. * @since 3.2 */ public boolean isUsePreparedStatements() { return usePreparedStatements; } /** * Enable/disable {@link PreparedStatement prepared statements} usage. If enabled (default), then all persistence * methods (such as {@link #select}, {@link #update}, and others) will make use of prepared statements. Note that * methods accepting a {@link Statement} must be called with {@link SimpleStatement} instances to participate in * statement preparation. * * @param usePreparedStatements whether to use prepared statements. * @since 3.2 */ public void setUsePreparedStatements(boolean usePreparedStatements) { this.usePreparedStatements = usePreparedStatements; } /** * Returns the {@link EntityOperations} used to perform data access operations on an entity inside a Cassandra data * source. * * @return the configured {@link EntityOperations} for this template. * @see org.springframework.data.cassandra.core.EntityOperations */ protected EntityOperations getEntityOperations() { return this.entityOperations; } /** * Returns a reference to the configured {@link ProjectionFactory} used by this template to process CQL query * projections. * * @return a reference to the configured {@link ProjectionFactory} used by this template to process CQL query * projections. * @see org.springframework.data.projection.SpelAwareProxyProjectionFactory * @since 2.1 * @deprecated since 3.4, use {@link CassandraConverter#getProjectionFactory()} instead. */ protected SpelAwareProxyProjectionFactory getProjectionFactory() { return (SpelAwareProxyProjectionFactory) getConverter().getProjectionFactory(); } private CassandraPersistentEntity getRequiredPersistentEntity(Class entityType) { return getEntityOperations().getRequiredPersistentEntity(entityType); } /** * Returns the {@link StatementFactory} used by this template to construct and run Cassandra CQL statements. * * @return the {@link StatementFactory} used by this template to construct and run Cassandra CQL statements. * @see org.springframework.data.cassandra.core.StatementFactory * @since 2.1 */ protected StatementFactory getStatementFactory() { return this.statementFactory; } @Override public CqlIdentifier getTableName(Class entityClass) { return getEntityOperations().getTableName(entityClass); } // ------------------------------------------------------------------------- // Methods dealing with static CQL // ------------------------------------------------------------------------- @Override public List select(String cql, Class entityClass) { Assert.hasText(cql, "CQL must not be empty"); return select(SimpleStatement.newInstance(cql), entityClass); } @Override public T selectOne(String cql, Class entityClass) { Assert.hasText(cql, "CQL must not be empty"); Assert.notNull(entityClass, "Entity type must not be null"); return selectOne(SimpleStatement.newInstance(cql), entityClass); } @Override public Stream stream(String cql, Class entityClass) throws DataAccessException { Assert.hasText(cql, "CQL must not be empty"); Assert.notNull(entityClass, "Entity type must not be null"); return stream(SimpleStatement.newInstance(cql), entityClass); } // ------------------------------------------------------------------------- // Methods dealing with com.datastax.oss.driver.api.core.cql.Statement // ------------------------------------------------------------------------- @Override public ResultSet execute(Statement statement) { Assert.notNull(statement, "Statement must not be null"); return doQueryForResultSet(statement); } @Override public List select(Statement statement, Class entityClass) { Assert.notNull(statement, "Statement must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); Function mapper = getMapper(EntityProjection.nonProjecting(entityClass), EntityQueryUtils.getTableName(statement)); return doQuery(statement, (row, rowNum) -> mapper.apply(row)); } @Override public T selectOne(Statement statement, Class entityClass) { List result = select(statement, entityClass); return result.isEmpty() ? null : result.get(0); } @Override public Slice slice(Statement statement, Class entityClass) { Assert.notNull(statement, "Statement must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); ResultSet resultSet = doQueryForResultSet(statement); Function mapper = getMapper(EntityProjection.nonProjecting(entityClass), EntityQueryUtils.getTableName(statement)); return EntityQueryUtils.readSlice(resultSet, (row, rowNum) -> mapper.apply(row), 0, getEffectivePageSize(statement)); } @Override public Stream stream(Statement statement, Class entityClass) throws DataAccessException { Assert.notNull(statement, "Statement must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); Function mapper = getMapper(EntityProjection.nonProjecting(entityClass), EntityQueryUtils.getTableName(statement)); return doQueryForStream(statement, (row, rowNum) -> mapper.apply(row)); } // ------------------------------------------------------------------------- // Methods dealing with org.springframework.data.cassandra.core.query.Query // ------------------------------------------------------------------------- @Override public List select(Query query, Class entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); return doSelect(query, entityClass, getTableName(entityClass), entityClass); } List doSelect(Query query, Class entityClass, CqlIdentifier tableName, Class returnType) { CassandraPersistentEntity entity = getRequiredPersistentEntity(entityClass); EntityProjection projection = entityOperations.introspectProjection(returnType, entityClass); Columns columns = getStatementFactory().computeColumnsForProjection(projection, query.getColumns(), entity, returnType); Query queryToUse = query.columns(columns); StatementBuilder select = getStatementFactory().select(query, getRequiredPersistentEntity(entityClass)); return slice(select.build(), entityClass); } @Override public Stream stream(Query query, Class entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); return doStream(query, entityClass, getTableName(entityClass), entityClass); } Stream doStream(Query query, Class entityClass, CqlIdentifier tableName, Class returnType) { StatementBuilder countStatement = getStatementFactory().count(query, getRequiredPersistentEntity(entityClass), tableName); return doQueryForObject(countStatement.build(), Long.class); } @Override public boolean exists(Object id, Class entityClass) { Assert.notNull(id, "Id must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); CassandraPersistentEntity entity = getRequiredPersistentEntity(entityClass); StatementBuilder select = getStatementFactory().select(query.limit(1), getRequiredPersistentEntity(entityClass), tableName); return doQueryForResultSet(select.build()).one() != null; } @Override public T selectOneById(Object id, Class entityClass) { Assert.notNull(id, "Id must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); CassandraPersistentEntity entity = getRequiredPersistentEntity(entityClass); CqlIdentifier tableName = entity.getTableName(); StatementBuilder