Java tutorial
/* * Copyright 2016-2019 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 lombok.Value; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; import java.util.stream.StreamSupport; 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.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.CqlIdentifier; 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.QueryOptions; import org.springframework.data.cassandra.core.cql.SessionCallback; import org.springframework.data.cassandra.core.cql.WriteOptions; import org.springframework.data.cassandra.core.cql.session.DefaultSessionFactory; import org.springframework.data.cassandra.core.mapping.CassandraPersistentEntity; import org.springframework.data.cassandra.core.mapping.CassandraPersistentProperty; 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.mapping.context.MappingContext; 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.driver.core.RegularStatement; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; import com.datastax.driver.core.Session; import com.datastax.driver.core.SimpleStatement; import com.datastax.driver.core.Statement; import com.datastax.driver.core.exceptions.DriverException; import com.datastax.driver.core.querybuilder.Delete; import com.datastax.driver.core.querybuilder.Insert; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Truncate; import com.datastax.driver.core.querybuilder.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. * <p> * Can be used within a service implementation via direct instantiation with a {@link Session} reference, or get * prepared in an application context and given to services as bean reference. * <p> * Note: The {@link Session} 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 * @see org.springframework.data.cassandra.core.CassandraOperations * @since 2.0 */ public class CassandraTemplate implements CassandraOperations, ApplicationEventPublisherAware, ApplicationContextAware { private @Nullable ApplicationEventPublisher eventPublisher; private @Nullable EntityCallbacks entityCallbacks; private final CassandraConverter converter; private final CqlOperations cqlOperations; private final EntityOperations entityOperations; private final MappingContext<? extends CassandraPersistentEntity<?>, CassandraPersistentProperty> mappingContext; private final SpelAwareProxyProjectionFactory projectionFactory; private final StatementFactory statementFactory; /** * Creates an instance of {@link CassandraTemplate} initialized with the given {@link Session} and a default * {@link MappingCassandraConverter}. * * @param session {@link Session} used to interact with Cassandra; must not be {@literal null}. * @see CassandraConverter * @see Session */ public CassandraTemplate(Session session) { this(session, newConverter()); } /** * Creates an instance of {@link CassandraTemplate} initialized with the given {@link Session} and * {@link CassandraConverter}. * * @param session {@link Session} 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 Session */ public CassandraTemplate(Session 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 Session */ 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.getMappingContext()); this.mappingContext = converter.getMappingContext(); this.projectionFactory = new SpelAwareProxyProjectionFactory(); this.statementFactory = new StatementFactory(new QueryMapper(converter), new UpdateMapper(converter)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#batchOps() */ @Override public CassandraBatchOperations batchOps() { return new CassandraBatchTemplate(this); } /* (non-Javadoc) * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher) */ @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.eventPublisher = applicationEventPublisher; } /* (non-Javadoc) * @see org.springframework.context.ApplicationContextAware(org.springframework.context.ApplicationContext) */ @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (entityCallbacks == null) { setEntityCallbacks(EntityCallbacks.create(applicationContext)); } projectionFactory.setBeanFactory(applicationContext); projectionFactory.setBeanClassLoader(applicationContext.getClassLoader()); } /** * Configure {@link EntityCallbacks} to pre-/post-process entities during persistence operations. * * @param entityCallbacks */ public void setEntityCallbacks(@Nullable EntityCallbacks entityCallbacks) { this.entityCallbacks = entityCallbacks; } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#getConverter() */ @Override public CassandraConverter getConverter() { return this.converter; } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#CqlOperations() */ @Override public CqlOperations getCqlOperations() { return this.cqlOperations; } /** * 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 */ protected SpelAwareProxyProjectionFactory getProjectionFactory() { return this.projectionFactory; } 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; } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#getTableName(java.lang.Class) */ @Override public CqlIdentifier getTableName(Class<?> entityClass) { return getEntityOperations().getTableName(entityClass); } // ------------------------------------------------------------------------- // Methods dealing with static CQL // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#select(java.lang.String, java.lang.Class) */ @Override public <T> List<T> select(String cql, Class<T> entityClass) { Assert.hasText(cql, "CQL must not be empty"); return select(new SimpleStatement(cql), entityClass); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(java.lang.String, java.lang.Class) */ @Override public <T> T selectOne(String cql, Class<T> entityClass) { Assert.hasText(cql, "CQL must not be empty"); Assert.notNull(entityClass, "Entity type must not be null"); return selectOne(new SimpleStatement(cql), entityClass); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#stream(java.lang.String, java.lang.Class) */ @Override public <T> Stream<T> stream(String cql, Class<T> entityClass) throws DataAccessException { Assert.hasText(cql, "CQL must not be empty"); Assert.notNull(entityClass, "Entity type must not be null"); return stream(new SimpleStatement(cql), entityClass); } // ------------------------------------------------------------------------- // Methods dealing with com.datastax.driver.core.Statement // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#select(com.datastax.driver.core.Statement, java.lang.Class) */ @Override public <T> List<T> select(Statement statement, Class<T> entityClass) { Assert.notNull(statement, "Statement must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement)); return getCqlOperations().query(statement, (row, rowNum) -> mapper.apply(row)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(com.datastax.driver.core.Statement, java.lang.Class) */ @Override public <T> T selectOne(Statement statement, Class<T> entityClass) { return select(statement, entityClass).stream().findFirst().orElse(null); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#slice(com.datastax.driver.core.Statement, java.lang.Class) */ @Override public <T> Slice<T> slice(Statement statement, Class<T> entityClass) { Assert.notNull(statement, "Statement must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); ResultSet resultSet = getCqlOperations().queryForResultSet(statement); Function<Row, T> mapper = getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement)); return EntityQueryUtils.readSlice(resultSet, (row, rowNum) -> mapper.apply(row), 0, getEffectiveFetchSize(statement)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#stream(com.datastax.driver.core.Statement, java.lang.Class) */ @Override public <T> Stream<T> stream(Statement statement, Class<T> entityClass) throws DataAccessException { Assert.notNull(statement, "Statement must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); ResultSet resultSet = getCqlOperations().queryForResultSet(statement); return StreamSupport.stream(resultSet.spliterator(), false) .map(getMapper(entityClass, entityClass, EntityQueryUtils.getTableName(statement))); } // ------------------------------------------------------------------------- // Methods dealing with org.springframework.data.cassandra.core.query.Query // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#select(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public <T> List<T> select(Query query, Class<T> 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); } <T> List<T> doSelect(Query query, Class<?> entityClass, CqlIdentifier tableName, Class<T> returnType) { CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entityClass); Columns columns = getStatementFactory().computeColumnsForProjection(query.getColumns(), persistentEntity, returnType); Query queryToUse = query.columns(columns); RegularStatement select = getStatementFactory().select(queryToUse, persistentEntity, tableName); Function<Row, T> mapper = getMapper(entityClass, returnType, tableName); return getCqlOperations().query(select, (row, rowNum) -> mapper.apply(row)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#selectOne(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public <T> T selectOne(Query query, Class<T> entityClass) throws DataAccessException { List<T> result = select(query, entityClass); return result.isEmpty() ? null : result.get(0); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#slice(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public <T> Slice<T> slice(Query query, Class<T> entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); RegularStatement select = getStatementFactory().select(query, getRequiredPersistentEntity(entityClass)); return slice(select, entityClass); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#stream(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public <T> Stream<T> stream(Query query, Class<T> 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); } <T> Stream<T> doStream(Query query, Class<?> entityClass, CqlIdentifier tableName, Class<T> returnType) { RegularStatement statement = getStatementFactory().select(query, getRequiredPersistentEntity(entityClass), tableName); ResultSet resultSet = getCqlOperations().queryForResultSet(statement); return StreamSupport.stream(resultSet.spliterator(), false) .map(getMapper(entityClass, returnType, tableName)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#update(org.springframework.data.cassandra.core.query.Query, org.springframework.data.cassandra.core.query.Update, java.lang.Class) */ @Override public boolean update(Query query, org.springframework.data.cassandra.core.query.Update update, Class<?> entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(update, "Update must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); Statement updateStatement = getStatementFactory().update(query, update, getRequiredPersistentEntity(entityClass)); return getCqlOperations().execute(updateStatement); } @Nullable WriteResult doUpdate(Query query, org.springframework.data.cassandra.core.query.Update update, Class<?> entityClass, CqlIdentifier tableName) { RegularStatement updateStatement = getStatementFactory().update(query, update, getRequiredPersistentEntity(entityClass), tableName); return getCqlOperations().execute(new StatementCallback(updateStatement)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#delete(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public boolean delete(Query query, Class<?> entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); WriteResult result = doDelete(query, entityClass, getTableName(entityClass)); return result != null && result.wasApplied(); } @Nullable WriteResult doDelete(Query query, Class<?> entityClass, CqlIdentifier tableName) { RegularStatement delete = getStatementFactory().delete(query, getRequiredPersistentEntity(entityClass), tableName); maybeEmitEvent(new BeforeDeleteEvent<>(delete, entityClass, tableName)); WriteResult writeResult = getCqlOperations().execute(new StatementCallback(delete)); maybeEmitEvent(new AfterDeleteEvent<>(delete, entityClass, tableName)); return writeResult; } // ------------------------------------------------------------------------- // Methods dealing with entities // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#count(java.lang.Class) */ @Override public long count(Class<?> entityClass) { Assert.notNull(entityClass, "Entity type must not be null"); Select select = QueryBuilder.select().countAll().from(getTableName(entityClass).toCql()); Long count = getCqlOperations().queryForObject(select, Long.class); return count != null ? count : 0L; } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#count(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public long count(Query query, Class<?> entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); return doCount(query, entityClass, getTableName(entityClass)); } long doCount(Query query, Class<?> entityClass, CqlIdentifier tableName) { RegularStatement countStatement = getStatementFactory().count(query, getRequiredPersistentEntity(entityClass), tableName); Long count = getCqlOperations().queryForObject(countStatement, Long.class); return count != null ? count : 0L; } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#exists(java.lang.Object, java.lang.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); Select select = QueryBuilder.select().from(getTableName(entityClass).toCql()); getConverter().write(id, select.where(), entity); return getCqlOperations().queryForResultSet(select).iterator().hasNext(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#exists(org.springframework.data.cassandra.core.query.Query, java.lang.Class) */ @Override public boolean exists(Query query, Class<?> entityClass) throws DataAccessException { Assert.notNull(query, "Query must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); return doExists(query, entityClass, getTableName(entityClass)); } boolean doExists(Query query, Class<?> entityClass, CqlIdentifier tableName) { RegularStatement select = getStatementFactory().select(query.limit(1), getRequiredPersistentEntity(entityClass), tableName); return getCqlOperations().queryForResultSet(select).iterator().hasNext(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#selectOneById(java.lang.Object, java.lang.Class) */ @Override public <T> T selectOneById(Object id, Class<T> entityClass) { Assert.notNull(id, "Id must not be null"); Assert.notNull(entityClass, "Entity type must not be null"); CqlIdentifier tableName = getTableName(entityClass); Select select = QueryBuilder.select().all().from(tableName.toCql()); getConverter().write(id, select.where(), getRequiredPersistentEntity(entityClass)); Function<Row, T> mapper = getMapper(entityClass, entityClass, tableName); List<T> result = getCqlOperations().query(select, (row, rowNum) -> mapper.apply(row)); return result.isEmpty() ? null : result.get(0); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object) */ @Override public <T> T insert(T entity) { return insert(entity, InsertOptions.empty()).getEntity(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#insert(java.lang.Object, org.springframework.data.cassandra.core.InsertOptions) */ @Override public <T> EntityWriteResult<T> insert(T entity, InsertOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "InsertOptions must not be null"); return doInsert(entity, options, getTableName(entity.getClass())); } <T> EntityWriteResult<T> doInsert(T entity, WriteOptions options, CqlIdentifier tableName) { AdaptibleEntity<T> source = getEntityOperations().forEntity(maybeCallBeforeConvert(entity, tableName), getConverter().getConversionService()); CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass()); T entityToUse = source.isVersionedEntity() ? source.initializeVersionProperty() : entity; Insert insert = EntityQueryUtils.createInsertQuery(tableName.toCql(), entityToUse, options, getConverter(), persistentEntity); return source.isVersionedEntity() ? doInsertVersioned(insert.ifNotExists(), entityToUse, source, tableName) : doInsert(insert, entityToUse, tableName); } private <T> EntityWriteResult<T> doInsertVersioned(Insert insert, T entity, AdaptibleEntity<T> source, CqlIdentifier tableName) { return executeSave(entity, tableName, insert, result -> { if (!result.wasApplied()) { throw new OptimisticLockingFailureException( String.format("Cannot insert entity %s with version %s into table %s as it already exists", entity, source.getVersion(), tableName)); } }); } private <T> EntityWriteResult<T> doInsert(Insert insert, T entity, CqlIdentifier tableName) { return executeSave(entity, tableName, insert); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object) */ @Override public <T> T update(T entity) { return update(entity, UpdateOptions.empty()).getEntity(); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#update(java.lang.Object, org.springframework.data.cassandra.core.UpdateOptions) */ @Override public <T> EntityWriteResult<T> update(T entity, UpdateOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "UpdateOptions must not be null"); AdaptibleEntity<T> source = getEntityOperations().forEntity(entity, getConverter().getConversionService()); CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass()); CqlIdentifier tableName = persistentEntity.getTableName(); T entityToUpdate = maybeCallBeforeConvert(entity, tableName); return source.isVersionedEntity() ? doUpdateVersioned(entityToUpdate, options, tableName, persistentEntity) : doUpdate(entityToUpdate, options, tableName, persistentEntity); } private <T> EntityWriteResult<T> doUpdateVersioned(T entity, UpdateOptions options, CqlIdentifier tableName, CassandraPersistentEntity<?> persistentEntity) { AdaptibleEntity<T> source = getEntityOperations().forEntity(entity, getConverter().getConversionService()); Number previousVersion = source.getVersion(); T toSave = source.incrementVersion(); Update update = getStatementFactory().update(toSave, options, getConverter(), persistentEntity, tableName); return executeSave(toSave, tableName, source.appendVersionCondition(update, previousVersion), result -> { if (!result.wasApplied()) { throw new OptimisticLockingFailureException(String.format( "Cannot save entity %s with version %s to table %s. Has it been modified meanwhile?", toSave, source.getVersion(), tableName)); } }); } private <T> EntityWriteResult<T> doUpdate(T entity, UpdateOptions options, CqlIdentifier tableName, CassandraPersistentEntity<?> persistentEntity) { Update update = getStatementFactory().update(entity, options, getConverter(), persistentEntity, tableName); return executeSave(entity, tableName, update); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object) */ @Override public void delete(Object entity) { delete(entity, QueryOptions.empty()); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#delete(java.lang.Object, org.springframework.data.cassandra.core.cql.QueryOptions) */ @Override public WriteResult delete(Object entity, QueryOptions options) { Assert.notNull(entity, "Entity must not be null"); Assert.notNull(options, "QueryOptions must not be null"); AdaptibleEntity<Object> source = getEntityOperations().forEntity(entity, getConverter().getConversionService()); CassandraPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entity.getClass()); CqlIdentifier tableName = persistentEntity.getTableName(); Delete delete = getStatementFactory().delete(entity, options, getConverter(), persistentEntity, tableName); return source.isVersionedEntity() ? doDeleteVersioned(delete, entity, source, tableName) : doDelete(delete, entity, tableName); } private WriteResult doDeleteVersioned(Delete delete, Object entity, AdaptibleEntity<Object> source, CqlIdentifier tableName) { return executeDelete(entity, tableName, source.appendVersionCondition(delete), result -> { if (!result.wasApplied()) { throw new OptimisticLockingFailureException(String.format( "Cannot delete entity %s with version %s in table %s. Has it been modified meanwhile?", entity, source.getVersion(), tableName)); } }); } private WriteResult doDelete(Delete delete, Object entity, CqlIdentifier tableName) { return executeDelete(entity, tableName, delete, result -> { }); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#deleteById(java.lang.Object, java.lang.Class) */ @Override public boolean deleteById(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(); Delete delete = QueryBuilder.delete().from(tableName.toCql()); getConverter().write(id, delete.where(), entity); maybeEmitEvent(new BeforeDeleteEvent<>(delete, entityClass, tableName)); boolean result = getCqlOperations().execute(delete); maybeEmitEvent(new AfterDeleteEvent<>(delete, entityClass, tableName)); return result; } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.CassandraOperations#truncate(java.lang.Class) */ @Override public void truncate(Class<?> entityClass) { Assert.notNull(entityClass, "Entity type must not be null"); CqlIdentifier tableName = getTableName(entityClass); Truncate truncate = QueryBuilder.truncate(tableName.toCql()); maybeEmitEvent(new BeforeDeleteEvent<>(truncate, entityClass, tableName)); getCqlOperations().execute(truncate); maybeEmitEvent(new AfterDeleteEvent<>(truncate, entityClass, tableName)); } // ------------------------------------------------------------------------- // Fluent API entry points // ------------------------------------------------------------------------- /* (non-Javadoc) * @see org.springframework.data.cassandra.core.ExecutableSelectOperation#query(java.lang.Class) */ @Override public <T> ExecutableSelect<T> query(Class<T> domainType) { return new ExecutableSelectOperationSupport(this).query(domainType); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.ExecutableInsertOperation#insert(java.lang.Class) */ @Override public <T> ExecutableInsert<T> insert(Class<T> domainType) { return new ExecutableInsertOperationSupport(this).insert(domainType); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.ExecutableUpdateOperation#update(java.lang.Class) */ @Override public ExecutableUpdate update(Class<?> domainType) { return new ExecutableUpdateOperationSupport(this).update(domainType); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.ExecutableDeleteOperation#remove(java.lang.Class) */ @Override public ExecutableDelete delete(Class<?> domainType) { return new ExecutableDeleteOperationSupport(this).delete(domainType); } // ------------------------------------------------------------------------- // Implementation hooks and utility methods // ------------------------------------------------------------------------- private <T> EntityWriteResult<T> executeSave(T entity, CqlIdentifier tableName, Statement statement) { return executeSave(entity, tableName, statement, ignore -> { }); } private <T> EntityWriteResult<T> executeSave(T entity, CqlIdentifier tableName, Statement statement, Consumer<WriteResult> resultConsumer) { maybeEmitEvent(new BeforeSaveEvent<>(entity, tableName, statement)); T entityToSave = maybeCallBeforeSave(entity, tableName, statement); WriteResult result = getCqlOperations().execute(new StatementCallback(statement)); resultConsumer.accept(result); maybeEmitEvent(new AfterSaveEvent<>(entityToSave, tableName)); return EntityWriteResult.of(result, entityToSave); } private WriteResult executeDelete(Object entity, CqlIdentifier tableName, Statement statement, Consumer<WriteResult> resultConsumer) { maybeEmitEvent(new BeforeDeleteEvent<>(statement, entity.getClass(), tableName)); WriteResult result = getCqlOperations().execute(new StatementCallback(statement)); resultConsumer.accept(result); maybeEmitEvent(new AfterDeleteEvent<>(statement, entity.getClass(), tableName)); return result; } private int getConfiguredFetchSize(Session session) { return session.getCluster().getConfiguration().getQueryOptions().getFetchSize(); } @SuppressWarnings("ConstantConditions") private int getEffectiveFetchSize(Statement statement) { if (statement.getFetchSize() > 0) { return statement.getFetchSize(); } if (getCqlOperations() instanceof CassandraAccessor) { CassandraAccessor accessor = (CassandraAccessor) getCqlOperations(); if (accessor.getFetchSize() != -1) { return accessor.getFetchSize(); } } return getCqlOperations().execute(this::getConfiguredFetchSize); } @SuppressWarnings("unchecked") private <T> Function<Row, T> getMapper(Class<?> entityType, Class<T> targetType, CqlIdentifier tableName) { Class<?> typeToRead = resolveTypeToRead(entityType, targetType); return row -> { maybeEmitEvent(new AfterLoadEvent<>(row, targetType, tableName)); Object source = getConverter().read(typeToRead, row); T result = (T) (targetType.isInterface() ? getProjectionFactory().createProjection(targetType, source) : source); maybeEmitEvent(new AfterConvertEvent<>(row, result, tableName)); return result; }; } private Class<?> resolveTypeToRead(Class<?> entityType, Class<?> targetType) { return targetType.isInterface() || targetType.isAssignableFrom(entityType) ? entityType : targetType; } private static MappingCassandraConverter newConverter() { MappingCassandraConverter converter = new MappingCassandraConverter(); converter.afterPropertiesSet(); return converter; } protected <E extends CassandraMappingEvent<T>, T> void maybeEmitEvent(E event) { if (this.eventPublisher != null) { this.eventPublisher.publishEvent(event); } } protected <T> T maybeCallBeforeConvert(T object, CqlIdentifier tableName) { if (null != entityCallbacks) { return (T) entityCallbacks.callback(BeforeConvertCallback.class, object, tableName); } return object; } protected <T> T maybeCallBeforeSave(T object, CqlIdentifier tableName, Statement statement) { if (null != entityCallbacks) { return (T) entityCallbacks.callback(BeforeSaveCallback.class, object, tableName, statement); } return object; } @Value static class StatementCallback implements SessionCallback<WriteResult>, CqlProvider { @lombok.NonNull Statement statement; /* (non-Javadoc) * @see org.springframework.data.cassandra.core.cql.SessionCallback#doInSession(org.springframework.data.cassandra.Session) */ @Override public WriteResult doInSession(Session session) throws DriverException, DataAccessException { return WriteResult.of(session.execute(this.statement)); } /* (non-Javadoc) * @see org.springframework.data.cassandra.core.cql.CqlProvider#getCql() */ @Override public String getCql() { return this.statement.toString(); } } }