-
Notifications
You must be signed in to change notification settings - Fork 92
Achilles Custom Types
To support all Cassandra specific but powerful features such as tunable consistency levels or counters, Achilles introduces custom Java types:
#### ConsistencyLevel It is an enum exposing all existing consistency levels in Cassandra:
- ANY
- ONE
- TWO
- THREE
- QUORUM
- LOCAL_ONE
- LOCAL_QUORUM
- EACH_QUORUM
- ALL
- LOCAL_SERIAL
- SERIAL
See Consistency Level for more details
#### Counter This type represents a **Cassandra** counter column. It exposes the following methods:
public Long get();
public void incr();
public void incr(Long increment);
public void decr();
public void decr(Long decrement);
For more details on counters, see Counter type
#### CounterBuilder
The above Counter
type can be retrieved only from a managed entity. If you want to insert a transient entity having counter fields, you should use the CounterBuilder
type provided by Achilles
and call the get()
method to have a handle on the counter proxy.
The builder exposes the following static methods:
public static Counter incr()
public static Counter incr(Long incr)
public static Counter decr()
public static Counter decr(Long decr)
The only sensible usage for CounterBuilder
is for transient entity persistence. Example:
@Entity
public class UserEntity
{
@Id
private Long userId;
@Column
private Counter
private UserEntity(Long userId,.....,Counter popularity)
{
this.userId = userId;
...
this.popularity = popularity;
}
...
}
// Creating a new user with initial popularity value set to 100
manager.insert(new UserEntity(10L,....,CounterBuilder.incr(100L));
#### Options
An Options
is just a holder object for Cassandra specific TTL, Timestamp, Consistency level and other parameters
class Options {
ConsistencyLevel consistency;
Integer ttl;
Long timestamp;
boolean ifNotExists;
List<CasCondition> casConditions;
Optional<CASResultListener> casResultListenerO = Optional.absent();
Optional<com.datastax.driver.core.ConsistencyLevel> serialConsistencyO = Optional.absent();
public Optional<ConsistencyLevel> getConsistencyLevel() {
return Optional.fromNullable(consistency);
}
public Optional<Integer> getTtl() {
return Optional.fromNullable(ttl);
}
public Optional<Long> getTimestamp() {
return Optional.fromNullable(timestamp);
}
public boolean isIfNotExists() {
return ifNotExists;
}
public List<CasCondition> getCasConditions() {
return casConditions;
}
public boolean hasCasConditions() {
return CollectionUtils.isNotEmpty(casConditions);
}
public Optional<CASResultListener> getCasResultListener() {
return casResultListenerO;
}
public Optional<com.datastax.driver.core.ConsistencyLevel> getSerialConsistency() {
return serialConsistencyO;
}
}
Options are used in conjunction with common Persistence Manager operations insert()
, update()
, find()
, getProxy()
and remove()
.
Normally you cannot instantiate an Options
object yourself, you need to use the OptionsBuilder
instead, check below.
#### OptionsBuilder
Main builder to create Options
.
The exposed methods are:
Options options;
// Consistency
options = OptionsBuilder.withConsistency(QUORUM);
// TTL
options = OptionsBuilder.withTtl(10);
// Timestamp
options = OptionsBuilder.withTimestamp(100L);
// CAS 'IF NOT EXISTS'
options = OptionsBuilder.ifNotExists();
// CAS update conditions
options = OptionsBuilder.ifConditions(Arrays.asList(
new CASCondition("name","John"),
new CASCondition("age_in_years",33L));
// CAS result listener
options = OptionsBuilder.casResultListener(listener);
// CAS LOCAL_SERIAL instead of the default SERIAL value
options = OptionsBuilder.casLocalSerial();
// Multiple options
options = OptionsBuilder.withTtl(11)
.withConsistency(ANY)
.withTimestamp(111L);
#### IndexCondition
This class defines an index condition necessary for Indexed Query
Below is the public constructor of an IndexCondition
instance.
/**
* Shortcut constructor to build an EQUAL index condition
*
* @param columnName
* name of indexed column
* @param columnValue
* value of indexed column
*/
public IndexCondition(String columnName, Object columnValue) {
this.columnName = columnName;
this.indexRelation = IndexRelation.EQUAL;
this.columnValue = columnValue;
}
As you can notice, the support for secondary index is limited to EQUALITY clause. Inequalities on secondary indices do not provide predictable performance so Achilles does not support it by default.
#### TypedMap
The native query API used to return a Map<String,Object>
as result. It is not very user-friendly because it forces you do manual type casting. Example:
Map<String,Object> columns = manager.nativeQuery("SELECT * FROM users WHERE userId = 10").getFirst();
String name = (String)columns.get("name"); // BAD !
Long age = (Long)columns.get("age"); // BAD !
TypedMap
is just an extension of Map<String,Object>
offering two extras methods for convenience:
@SuppressWarnings("unchecked")
public <T> T getTyped(String key) {
T value = null;
if (super.containsKey(key) && super.get(key) != null) {
value = (T) super.get(key);
return value;
}
return value;
}
public <T> T getTypedOr(String key, T defaultValue) {
if (super.containsKey(key)) {
return getTyped(key);
} else {
return defaultValue;
}
}
Of course there is no magic, the dirty casting you no longer do, getTyped()
will do it for you. The target type is passed at runtime while calling the method. getTypedOr()
lets you provide a fall-back value.
Example of usage:
TypedMap columns = manager.nativeQuery("SELECT * FROM users WHERE userId = 10").first();
// Explicit type (String) is passed to method invocation
String name = columns.<String>getTyped("name");
// No need to provide explicit type. The compiler will infer type in this case
Long age = columns.get("age");
#### Interceptor
This is an interface defining the contract for a lifecycle interceptor. For more details, please check Interceptors
public interface Interceptor<T>
{
public void onEvent(T entity);
public List<Event> events();
}
#### CASCondition
This class defines a CAS condition for conditional update
Below is the public constructor of a CasCondition
instance.
public CasCondition(String columnName, Object value) {
this.columnName = columnName;
this.value = value;
}
The columnName
parameter represents the CQL3 column name, not the field name of the entity.
Let's consider the following mapping:
@Column(name = "age_in_years")
private Long age;
@Column
private String name;
- CAS condition for
age
:new CasCondition("age_in_years",...)
- CAS condition for
name
:new CasCondition("name",...)
For more details on CAS operations, please refer to Distributed CAS
#### CASResultListener
For all CAS operations, Cassandra returns an [applied]
boolean column telling whether the operation has been successful or not. If a CAS update operation failed, Cassandra also returns the current values that differ from the ones used by the CAS update.
To intercepts the CAS operation result and current values, you can register a CASResultListener
using the Options
API (see above)
The signature of the CASResultListener
is:
public interface CASResultListener {
public void onCASSuccess();
public void onCASError(CASResult casResult);
}
The CASResult
type is defined as:
public class CASResult {
private final Operation operation;
private final TypedMap currentValues;
public CASResult(Operation operation, TypedMap currentValues) {
this.operation = operation;
this.currentValues = currentValues;
}
...
public static enum Operation {INSERT, UPDATE}
}
For more details on CAS operations, please refer to Distributed CAS
#### InsertStrategy
Define the insert strategy on an entity. Right now only 2 values are possible:
info.archinnov.achilles.type.InsertStrategy.NOT_NULL_FIELDS
info.archinnov.achilles.type.InsertStrategy.ALL_FIELDS
Upon call to insert()
, depending on the chosen strategy Achilles will
- insert all fields on the entity, even if they are null
- insert only non null fields of the entity
Check here for more details on the Insert strategy
#### Encoding
Define the encoding type for enum values. 2 choices are available:
-
info.archinnov.achilles.annotations.Enumerated.Encoding.NAME
encode enum using theirname()
-
info.archinnov.achilles.annotations.Enumerated.Encoding.ORDINAL
encode enum using theirordinal()
More details on Enum type
#### NamingStrategy
Define the naming strategy for the keyspace, table and column name. There are 3 possible strategies:
- SNAKE_CASE: transform all schema name using snake case
- CASE_SENSITIVE: enclose the name between double quotes (") for escaping the case
- LOWER_CASE: transform the name to lower case
Check here for more details on the Naming strategy
#### Codec
Define a codec to transform a source type into a target type.
The source type can be any Java type. The target type should be a Java type supported by Cassandra
public interface Codec<FROM, TO> {
Class<FROM> sourceType();
Class<TO> targetType();
TO encode(FROM fromJava) throws AchillesTranscodingException;
FROM decode(TO fromCassandra) throws AchillesTranscodingException;
}
Example of LongToString codec:
public class LongToString implements Codec<Long,String> {
@Override
public Class<Long> sourceType() {
return Long.class;
}
@Override
public Class<String> targetType() {
return String.class;
}
@Override
public String encode(Long fromJava) throws AchillesTranscodingException {
return fromJava.toString();
}
@Override
public Long decode(String fromCassandra) throws AchillesTranscodingException {
return Long.parseLong(fromCassandra);
}
}
The codecs are necessary to define TypeTransformer
-
Bootstraping Achilles at runtime
- Runtime Configuration Parameters
-
Manager
-
Consistency Level
-
Cassandra Options at runtime
-
Lightweight Transaction (LWT)
-
JSON Serialization
-
Interceptors
-
Bean Validation (JSR-303)