info.archinnov.achilles.persistence.PersistenceManager Maven / Gradle / Ivy
Show all versions of achilles-core Show documentation
/*
* Copyright (C) 2012-2014 DuyHai DOAN
*
* 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
*
* http://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 info.archinnov.achilles.persistence;
import static com.datastax.driver.core.BatchStatement.Type.LOGGED;
import static com.datastax.driver.core.BatchStatement.Type.UNLOGGED;
import static info.archinnov.achilles.options.OptionsBuilder.noOptions;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.datastax.driver.core.Statement;
import info.archinnov.achilles.internal.validation.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.datastax.driver.core.Session;
import info.archinnov.achilles.internal.context.ConfigurationContext;
import info.archinnov.achilles.internal.context.DaoContext;
import info.archinnov.achilles.internal.context.PersistenceContextFactory;
import info.archinnov.achilles.internal.metadata.holder.EntityMeta;
import info.archinnov.achilles.query.cql.NativeQuery;
import info.archinnov.achilles.query.slice.SliceQueryBuilder;
import info.archinnov.achilles.query.typed.TypedQuery;
import info.archinnov.achilles.type.IndexCondition;
import info.archinnov.achilles.options.Options;
/**
*
* Stateless object to manage entity persistence.
* This class is totally thread-safe and can be shared by many threads.
* You should normally have only one instance of PersistenceMananger across the application
*
*
* A PersistenceMananger is very cheap to create from an {@link info.archinnov.achilles.persistence.PersistenceManagerFactory}
*
*
*
*
I Persist transient entity
*
* // Insert
* MyEntity managedEntity = manager.insert(myEntity);
*
*
* II Update for modifications
*
* User managedUser = manager.find(User.class,1L);
* user.setFirstname("DuyHai");
*
* manager.update(user);
*
*
* III Deleting entities
*
* // Simple deletion
* User managedUser = manager.find(User.class,1L);
* manager.delete(managedUser);
*
* // Direct deletion without read-before-write
* manager.deleteById(User.class,1L);
*
*
* IV Loading entities
*
* // Read data from Cassandra
* User managedUser = manager.find(User.class,1L);
*
*
* V Creating proxy for update
*
* // No data read from Cassandra
* User managedUser = manager.forUpdate(User.class,1L);
* managedUser.setAge(30);
*
* // Direct update, no read from Cassandra has been done
* manager.update(managedUser);
*
*
* VI Reloading state for managed entities
*
* // Read data from Cassandra
* User managedUser = manager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Reload data from Cassandra into the managed entity
* manager.refresh(managedUser);
*
*
* VII Removing proxy from managed entities
*
* // Create managed entity
* User managedUser = manager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Removing proxy before passing it to client via serialization
* User transientUser = manager.removeProxy(managedUser);
*
*
* VIII Accessing native Session object
*
* Session session = manager.getNativeSession();
* ...
*
* // Issue simple CQL queries
* session.execute("UPDATE users SET age=:age WHERE id=:id",30,10);
*
*
* IX JSON serialization/deserialization
*
* // Serialize an object to JSON using the registered or default object mapper
* String json = manager.serializeToJSON(myModel);
* ...
*
* // Deserialize a JSON string into an object the registered or default object mapper
* MyModel myModel = manager.deserializeFromJSON(json);
*
*
* X Initializing all counter fields
*
* // Create managed entity
* User user = manager.find(User.class,1L);
* ...
* // Perform some logic
* ...
*
* // Load all lazy counter fields
* manager.initialize(user);
*
*
*
*
* @see Persistence Manager operations
*/
public class PersistenceManager extends CommonPersistenceManager {
private static final Logger log = LoggerFactory.getLogger(PersistenceManager.class);
protected PersistenceManager(Map, EntityMeta> entityMetaMap, PersistenceContextFactory contextFactory, DaoContext daoContext, ConfigurationContext configContext) {
super(entityMetaMap, contextFactory, daoContext, configContext);
}
/**
* Find an entity.
*
*
* // Read data from Cassandra
* User managedUser = manager.find(User.class,1L);
*
*
* @param entityClass
* Entity type
* @param primaryKey
* Primary key (Cassandra row key) of the entity to load
*
* @return T managed entity
*/
public T find(Class entityClass, Object primaryKey) {
log.debug("Find entity class '{}' with primary key '{}'", entityClass, primaryKey);
return super.asyncFind(entityClass, primaryKey, noOptions()).getImmediately();
}
/**
* Find an entity with the given Consistency Level for read
*
*
* // Read data from Cassandra
* User managedUser = manager.find(User.class,1L,QUORUM);
*
*
* @param entityClass
* Entity type
* @param primaryKey
* Primary key (Cassandra row key) of the entity to load
* @param options
* Options for read
*
* @return T managed entity
*/
public T find(final Class entityClass, final Object primaryKey, Options options) {
if (log.isDebugEnabled()) {
log.debug("Find entity class '{}' with primary key {} and options {}", entityClass, primaryKey, options);
}
return super.asyncFind(entityClass, primaryKey, options).getImmediately();
}
/**
* Create a proxy for the entity update. An new empty entity will be created,
* populated with the provided primary key and then proxified. This method
* never returns null. Use this method to perform direct update without
* read-before-write
*
*
* // No data read from Cassandra
* User proxifiedUser = manager.forUpdate(User.class,1L);
* proxifiedUser.setAge(33);
* manager.update(proxifiedUser);
*
*
* @param entityClass
* Entity type
* @param primaryKey
* Primary key (Cassandra row key) of the entity to initialize
*
* @return T proxy
*/
public T forUpdate(Class entityClass, Object primaryKey) {
log.debug("Get reference for entity class '{}' with primary key {}", entityClass, primaryKey);
return super.getProxyForUpdateInternal(entityClass, primaryKey);
}
/**
* Initialize all lazy counter fields of a set of 'managed' entities
*
*
* // Create a managed entity
* User user = manager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Initialize all counter fields not yet loaded into the managed entity
* manager.initialize(user);
*
*
* Raise an IllegalStateException if an entity is not 'managed'
*
*/
public T initialize(final T entity) {
if (log.isDebugEnabled()) {
log.debug("Force lazy fields initialization for entity {}", proxifier.removeProxy(entity));
}
return super.initialize(entity);
}
/**
* Initialize all lazy counter fields of a list of 'managed' entities
*
*
* // Create managed entities
* User user1 = manager.find(User.class,1L);
* User user2 = manager.find(User.class,2L);
* ...
* // Perform some logic
* ...
*
* // Initialize all counter fields not yet loaded into the managed entity
* manager.initialize(Sets.newHashSet(user1, user2));
*
*
* Raise an IllegalStateException if an entity is not 'managed'
*
*/
public Set initialize(final Set entities) {
log.debug("Force lazy fields initialization for entity set {}", entities);
return super.initialize(entities);
}
/**
* Initialize all lazy counter fields of a list of 'managed' entities
*
*
* // Create managed entities
* User user1 = manager.find(User.class,1L);
* User user2 = manager.find(User.class,2L);
* ...
* // Perform some logic
* ...
*
* // Initialize all counter fields not yet loaded into the managed entity
* manager.initialize(Arrays.asList(user1, user2));
*
*
* Raise an IllegalStateException if an entity is not 'managed'
*
*/
public List initialize(final List entities) {
log.debug("Force lazy fields initialization for entity set {}", entities);
return super.initialize(entities);
}
/**
* Shorthand for manager.removeProxy(manager.initialize(T entity))
*
*/
public T initAndRemoveProxy(T entity) {
return super.removeProxy(super.initialize(entity));
}
/**
* Shorthand for manager.removeProxy(manager.initialize(Set entities))
*
*/
public Set initAndRemoveProxy(Set entities) {
return super.removeProxy(super.initialize(entities));
}
/**
* Shorthand for manager.removeProxy(manager.initialize(List entities))
*
*/
public List initAndRemoveProxy(List entities) {
return super.removeProxy(super.initialize(entities));
}
/**
* Remove the proxy of a 'managed' entity and return the underlying "raw"
* entity
*
*
* If the argument is not a proxy objet, return itself
* Else, return the target object behind the proxy
*
*
* // Create managed entity
* User managedUser = manager.find(User.class,1L);
* ...
* // Perform some logic
*
* // Removing proxy before passing it to client via serialization
* User transientUser = manager.removeProxy(managedUser);
*
*
* @param proxy
* @return real object
*/
public T removeProxy(T proxy) {
log.debug("Removing proxy for entity {}", proxy);
return super.removeProxy(proxy);
}
/**
* Remove the proxy of a list of 'managed' entities and return the
* underlying "raw" entities
*
* See {@link #removeProxy}
*
* @param proxies
* list of proxified entity
* @return real object list
*/
public List removeProxy(List proxies) {
log.debug("Removing proxy for a list of entities {}", proxies);
return super.removeProxy(proxies);
}
/**
* Remove the proxy of a set of 'managed' entities return the underlying
* "raw" entities
*
* See {@link #removeProxy}
*
* @param proxies
* set of proxified entities
* @return real object set
*/
public Set removeProxy(Set proxies) {
log.debug("Removing proxy for a set of entities {}", proxies);
return super.removeProxy(proxies);
}
/**
* Create a builder to start slice query DSL. The provided entity class must be:
*
*
* - a entity type managed by Achilles
* - a clustered entity, slicing is irrelevant for non-clustered entity
*
*
* @see Slice query API
*
* @param entityClass type of the clustered entity
* @param : type of the clustered entity
* @return SliceQueryBuilder
*/
public SliceQueryBuilder sliceQuery(Class entityClass) {
log.debug("Execute slice query for entity class {}", entityClass);
final EntityMeta meta = super.validateSliceQueryInternal(entityClass);
return new SliceQueryBuilder<>(sliceQueryExecutor, entityClass, meta);
}
/**
* Return a CQL native query
*
*
*
*
* Native query without bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",Arrays.asList(10,11))).limit(20);
* List<TypedMap> actual = manager.nativeQuery(nativeQuery).get();
*
*
*
*
*
* Native query with bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",bindMarker())).limit(bindMarker());
* List<TypedMap> actual = manager.nativeQuery(nativeQuery,Arrays.asList(10,11),20).get();
*
*
* @see Native query API
*
* @param statement
* native CQL regularStatement, including limit, ttl and consistency
* options
*
* @param boundValues
* values to be bind to the parameterized query, if any
*
* @return NativeQuery
*/
public NativeQuery nativeQuery(Statement statement, Object... boundValues) {
log.debug("Execute native query {}", statement);
Validator.validateNotNull(statement, "The statement for native query should not be null");
return new NativeQuery(daoContext, configContext, statement, noOptions(), boundValues);
}
/**
* Return a CQL native query builder
*
*
*
*
* Native query without bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",Arrays.asList(10,11))).limit(20);
* List<TypedMap> actual = manager.nativeQuery(nativeQuery).get();
*
*
*
*
*
* Native query with bound values
*
* RegularStatement nativeQuery = select("name",age_in_years").from("UserEntity").where(in("id",bindMarker())).limit(bindMarker());
* List<TypedMap> actual = manager.nativeQuery(nativeQuery,Arrays.asList(10,11),20).get();
*
*
* @see Native query API
*
* @param statement
* native CQL statement, including limit, ttl and consistency
* options
*
* @param options
* options for the query. Only LWT Result listener passed as option is taken
* into account. For timestamp, TTL and LWT conditions you must specify them
* directly in the query string
*
* @param boundValues
* values to be bind to the parameterized query, if any
*
* @return NativeQuery
*/
public NativeQuery nativeQuery(Statement statement, Options options, Object... boundValues) {
log.debug("Execute native query {}", statement);
Validator.validateNotNull(statement, "The statement for native query should not be null");
return new NativeQuery(daoContext, configContext, statement, options, boundValues);
}
/**
* Return a CQL typed query builder
*
* All found entities will be in managed state
*
*
*
*
* Typed query without bound values
*
* RegularStatement nativeQuery = select().from("MyEntity").where().limit(3);
* List<MyEntity> actual = manager.typedQuery(MyEntity.class, nativeQuery).get();
*
*
*
*
*
* Typed query with bound values
*
* RegularStatement statement = select().from("MyEntity").limit(bindMarker());
* List<MyEntity> actual = manager.typedQuery(MyEntity.class, statement,3).get();
*
*
* @see Typed query API
*
* @param entityClass
* type of entity to be returned
*
* @param statement
* native CQL regularStatement, including limit, ttl and consistency
* options
*
* @param boundValues
* values to be bind to the parameterized query, if any
*
* @return TypedQuery
*/
public TypedQuery typedQuery(Class entityClass, Statement statement, Object... boundValues) {
log.debug("Execute typed query {}", statement);
final EntityMeta meta = super.typedQueryInternal(entityClass, statement, boundValues);
return new TypedQuery<>(entityClass, daoContext, configContext, statement, meta, contextFactory, boundValues);
}
/**
* Return a CQL typed query builder
*
* All found entities will be in 'managed' state
*
* @param entityClass
* type of entity to be returned
*
* @param indexCondition
* index condition
*
* @return TypedQuery
*/
public TypedQuery indexedQuery(Class entityClass, IndexCondition indexCondition) {
log.debug("Execute indexed query for entity class {}", entityClass);
final Statement statement = super.indexedQueryInternal(entityClass, indexCondition);
final EntityMeta meta = super.typedQueryInternal(entityClass, statement, indexCondition.getColumnValue());
return new TypedQuery<>(entityClass, daoContext, configContext, statement, meta, contextFactory, new Object[]{indexCondition.getColumnValue()});
}
/**
* Serialize the entity in JSON using a registered Object Mapper or default Achilles Object Mapper
* @param entity
* @return serialized entity in JSON
* @throws IOException
*/
public String serializeToJSON(Object entity) throws IOException {
return super.serializeToJSON(entity);
}
/**
* Deserialize the given JSON into entity using a registered Object Mapper or default Achilles Object Mapper
* @param type
* @param serialized
* @param
* @return deserialized entity from JSON
* @throws IOException
*/
public T deserializeFromJSON(Class type, String serialized) throws IOException {
return super.deserializeFromJSON(type, serialized);
}
/**
* Return Session object from Java Driver
* @return Session
*/
public Session getNativeSession() {
return super.getNativeSession();
}
/**
* Create a new state-full LOGGED Batch
*
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full Batch
*/
public Batch createLoggedBatch() {
log.debug("Create new Logged Batch instance");
return new Batch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, false);
}
/**
* Create a new state-full UNLOGGED Batch
*
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full Batch
*/
public Batch createUnloggedBatch() {
log.debug("Create new Unlogged Batch instance");
return new Batch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, false);
}
/**
* Create a new state-full ordered and LOGGED Batch
*
*
* This Batch respect insertion order by generating increasing timestamp with micro second resolution.
* If you use ordered Batch in multiple clients, do not forget to synchronize the clock between those clients
* to avoid statements interleaving
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full ordered Batch
*/
public Batch createOrderedLoggedBatch() {
log.debug("Create new ordered Logged Batch");
return new Batch(entityMetaMap, contextFactory, daoContext, configContext, LOGGED, true);
}
/**
* Create a new state-full ordered and UNLOGGED Batch
*
*
* This Batch respect insertion order by generating increasing timestamp with micro second resolution.
* If you use ordered Batch in multiple clients, do not forget to synchronize the clock between those clients
* to avoid statements interleaving
*
* WARNING : This Batch is state-full and not
* thread-safe. In case of exception, you MUST not re-use it but create
* another one
*
* @return a new state-full ordered Batch
*/
public Batch createOrderedUnloggedBatch() {
log.debug("Create new ordered Unlogged Batch");
return new Batch(entityMetaMap, contextFactory, daoContext, configContext, UNLOGGED, true);
}
}