For bean mapping, only field-based access type is supported by Achilles.
Furthermore, there is no default mapping for fields. If you want a field to be mapped, you must annotate it with @Column / @PartitionKey / @ClusteringColumn / @Computed.
All fields that are not annotated by either annotations is considered transient by Achilles
Achilles maps an entity into a Cassandra table. Each row represents an instance of the entity and each column represents a field.
For clustered entities, each tuple of (partition key, clustering column 1, ...clustering column N) represent a logical row although it translates into distinct physical columns by the underlying storage engine.
@Table@PartitionKey@Table annotation, it will be used as table name in Cassandra for the entityIn any case, the provided or derived table name should match the regexp pattern : [a-zA-Z0-9_]{1,48}
The limitation to 48 characters comes from Cassandra restriction on table name size
Examples:
@Table
public class MyEntity
{
@PartitionKey
private Long id;
...
}
Inferred table name = myentity
@Table(table = "another_entity")
public class AnotherEntity
{
@PartitionKey
private Long id;
...
}
Inferred table name = another_entity
A field is managed by Achilles if it is annotated with @Column / @PartitionKey / @ClusteringColumn / @Computed.
This is the explicit column mapping default behavior. You also can opt-in for an implicit column mapping behavior. See
Column Mapping Strategy for more details on this
@Column annotation.On
@PartitionKey/@ClusteringColumnfields, you can override the column name by adding a@Columnannotation.
Examples:
...
@Column("tweet_id")
@ClusteringColumn
private UUID tweetId;
...
Column name in Cassandra = name
...
@Column(name="age_in_year")
private Integer age;
...
Column name in Cassandra = age_in_year
It is possible to have values stored in Cassandra that is not mapped to any field/property of the entity. In this case Achilles will simply ignore them
The default strategy for field/column mapping is to use the @PartitionKey, @ClusteringColumn and @Column annotations. This is the explicit mapping strategy. However, you can choose an implicit mapping strategy so that Achilless will pick all fields of your Java bean as Cassandra columns, except those annotated by @Transient.
In any case, the @PartitionKey and @ClusteringColumn annotations are still required to define the primary key of your table.
You can specify the column mapping strategy at compile time using the @CompileTimeConfig annotation:
@CompileTimeConfig(cassandraVersion = CassandraVersion.CASSANDRA_3_0_X, columnMappingStrategy = ColumnMappingStrategy.IMPLICIT)
public interface AchillesConfig {
}
The default behavior is ColumnMappingStrategy.EXPLICIT
See Configuring Achilles at compile time for more details
Since Cassandra 2.0.6 it is possible to define static columns. A static column is a column which is related to the partition key(s).
There is only one distinct value of static column per partition key.
Consequently it is only possible to define static column within an entity having at least one @ClsuteringColumn annotation
To define a static column, use the @Static annotation
...
@Column
@Static
private Integer age;
...
With CQL, collection and maps are supported natively so there is nothing special to do for Achilles
Example:
...
@Column
private List<String> addresses;
...
In the CQL semantics, a null collection/map or an empty collection/map is equivalent. This is not the case in Java.
To avoid null check in client code, you can add the @EmptyCollectionIfNull annotation on the collection/map. This way when deserializing values from Cassandra, Achilles will instantiate an empty collection/map instead of null
Let's consider the following enums
public enum Country {
USA, CANADA, FRANCE, UNITED_KINGDOM, GERMANY ...
}
public enum Pricing {
PREMIUM, VALUE, ECONOMIC ...
}
Achilles does support enum types using the @Enumerated annotation. By default enums are serialized as String using the name() method.
Consequently, the ordering for enum types is based on their name, and not their natural ordering in Java (based on declaration order).
@Column(name = "pricing")
@Enumerated // or @Enumerated(Encoding.NAME)
private Pricing pricing;
This is an important detail and can be a gotcha if you don't pay attention when doing slice queries on entities having an enum as clustering column.
Achilles also lets you serialize the enums using their ordinal. In this case, you must change the value to Encoding.ORDINAL
@Column(name = "pricing")
@Enumerated(Encoding.ORDINAL)
private Pricing pricing;
The @Enumerated annotation also works on collections & maps.
@Column(name = "pricings")
private List<@Enumerated(Encoding.NAME) Pricing> pricings;
@Column(name = "pricing_per_country")
private Map<@Enumerated(Encoding.ORDINAL) Country, @Enumerated(Encoding.NAME) PricingType> pricingPerCountry;
Please note that when Achilles encounters an enum without the
@Enumeratedannotation (on simple value, collections or maps),
it will encode it using the name. This is the default behavior
Use the @Counter annotation on a column of type Long to indicate it is a counter column. Please note that if an entity contains a counter type, all other columns (normal and static) should also
be of counter type. However, clustering columns can be of type different than counter.
Example:
...
@Column
@Counter
private Long tweetsCount;
...
Achilles supports Cassandra UDT ( User Defined Type ) mapping. To mark a JavaBean as an UDT, just use the @UDT annotation:
@UDT(keyspace = "my_ks", name="user_udt")
public class UserUDT {
@Column
private String firstname;
@Column
private String lastname;
...
}
Then you can re-use the UDT class in another entity
@Table
public class Tweet {
@PartitionKey
private UUID tweetId;
@Column
private String content;
@Column
@Frozen
private UserUDT author;
}
It is necessary to use the @Frozen annotation on the UDT field
Achilles also supports CQL tuples using the custom Tuple types.
@Table
public class Entity {
...
@Column
private Tuple3<Integer, String, List<String>> tuple3;
}
There are 10 classes for tuple types: Tuple1, Tuple2, ..., Tuple10. See Tuples for more details
Note: it is not necessary to add
@Frozenon tuple columns because they are by default already frozen.
Nested collections, UDT or tuples do not need the@Frozenannotation either
It is possible to map the result of a function application to a column. For this, uses @Computed:
@Table
public class Entity {
...
@Column
private String value;
@Computed(function = "writetime", alias="writetime_value", targetColumns = {"value"}, cqlClass = Long.class)
private Long valueWriteTime;
}
Please note that field mapped to computed values are only retrieve for SELECT query. They will be ignored for INSERT/UPDATE/DELETE.
For more details, see @Computed
@Consistency annotation. This applies to all fieldsExample:
@Table(table = "user_tweets")
@Consistency(read = QUORUM, write = QUORUM, serial = SERIAL)
public class UseEntity {
@PartitionKey
private Long id;
@Column
private String name;
...
}
In the above example:
Achilles does support entity with only @PartitionKey / @ClusteringColumn fields. They are called value-less entities. A common use case for value-less entities is indexing data.
To map a Java UUID type to Cassandra timeuuid type, you need to add the @TimeUUID annotation on the target field.
To add a secondary index on a column, use the @Index annotation. For more information on the usage, check @Index
Achilles does support mapping inheritance. The strategy is one table per class. This is the most natural and straightforward strategy for inheritance.
Examples:
Case 1:
@Table(table = "parent")
public class ParentEntity {
@PartitionKey
protected Long id;
@Column
protected String name;
}
@Table(table = "child")
public class ChildEntity extends ParentEntity {
@Column
private String type;
}
With the above example, Achilles will create the following tables:
CREATE TABLE parent(
id bigint PRIMARY KEY,
name text
);
CREATE TABLE child(
id bigint PRIMARY KEY,
name text,
type text
);
Case 2:
public class ParentEntity {
@PartitionKey
private Long id;
}
@Table(table = "child1")
public class Child1Entity extends ParentEntity {
@Column
private String name;
}
@Table(table = "child2")
public class Child2Entity extends ParentEntity {
@Column
private String type;
}
For this case, Achilles will create the following tables:
CREATE TABLE child1(
id bigint PRIMARY KEY,
name text
);
CREATE TABLE child2(
id bigint PRIMARY KEY,
type text
);
Please note that all fields in parent classes should have
protectedorpackage protectedvisibility otherwise Achilles cannot access them and parse their annotations
You can define a strategy to transform the keyspace name, table name and column name defined on your entities. Available strategies are:
The naming strategy can be defined at 2 places, by their ascending order of priority:
However, all those strategies are overriden if you set the name on the column manually using the name attribute of the @Column annotation
Naming Strategy priority
| Priority (ascending order) | Description |
|---|---|
| 1 (lowest priority) | Global naming strategy defined at compile time on @CompileTimeConfig |
| 2 | Locally on each entity using the @Strategy annotation |
| 3 (highest priority) | Defined using the value attribute on the @Column annotation |
Example:
@Table(keyspace="myKeyspace", table="myTable)
@Strategy(naming = NamingStrategy.SNAKE_CASE)
// final keyspace name will be 'myKeyspace' and table name to 'myTable' because defined statically
public class MyBean
{
@PartitionKey
private Long id;
@Column
private String firstName; //column name transformed to 'first_name'
@Column(name = "misc")
private String customInfo; //column name will be 'misc' because name defined on @Column will override any NamingStrategy
...
}
The case of the column name defined with @Column will be respected by Achilles and if the column name has mixed upper & lower cases, Achilles will enclose it between double quotes when creating the table.
Example:
@Column("itemsCount")
private Long itemsCount;
The generated DDL script would be:
CREATE TABLE ...
(
...
"itemsCount" bigint,
...
)
Similarly, all generated CQL statements will enquote the column name.