Skip to content

Achilles Annotations

DuyHai DOAN edited this page Dec 24, 2014 · 25 revisions

For bean mapping, only field annotation (as opposed to getter/setter annotation) 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 / @CompoundPrimaryKey. All fields that are not annotated by either annotations are considered transient by Achilles

Below is a list of all Achilles annotations.


#### @Entity

Indicates that an entity is candidate for persistence. By default Achilles creates a table whose name is the short class name of the entity. If you want to define a specific keyspace name, set the keyspace attribute. If you want to define a specific table name, set the table attribute. If you want to define a specific comment on this table when Achilles generates the DDL script, set the comment attribute. Please note that all simple quotes ' in the comment will be escaped according to the CQL syntax

Example:

@Entity(keyspace = "back_end", table="users", comment = "table for users")
public class User 
{
	...
	...
}

> **Please note that Cassandra limits the column family name to 48 characters max (because of limitations in Windows for file path max lengths)**
#### @CompoundPrimaryKey

Indicates a field to be mapped as a compound primary key for an clustered entity. A valid compound primary key should be a POJO having at least 2 fields representing primary key components. When the name attribute of @CompoundPrimaryKey is given, the field will be mapped to this name. Otherwise the field name will be used.

The first component is the partition key, the remaining components are the clustering keys.

For more details, see Clustered Entity


#### @Column

Indicates a field to be mapped by Achilles. When the name attribute of @Column is given, the field will be mapped to this name. Otherwise the field name will be used.

Example:

@Column(name = "age_in_years")
private Long age; 

Remark: the custom name set on a column will override any NamingStrategy defined on the table/globally

To define a static column, just set the attribute staticColumn to true:

@Column(staticColumn = true)
private String name; 

#### @PartitionKey

This annotation indicates which field is a partition key for the table Put this annotation on several fields to have a composite partition key. The attribute value indicates the ordering of this partition component in a composite partition key. The attribute value defaults to 1.

Unlike Java indexes, the ordering starts at 1. It is a design choice since it is more natural for human being to start counting at 1

	private static class SimpleCompoundKey 
	{
		@PartitionKey
		private Long id;

		@ClusteringColumn
		private UUID date;
		...
	}
	...
	private static class CompositePartitionKey 
	{
		@PartitionKey(1)
		private Long id;

		@PartitionKey(2)
		private String type;

		@ClusteringColumn
		private UUID date;
		...
	}	
}

In the above example, id and type are part of the composite partition key. date is the clustering key.

Remark: all fields annotated with @PartitionKey should be consecutive with respect to their ordering. Failing this condition will raise an exception during Achilles bootstrap

For more detail on this annotation and its usage, please refer to Clustered Entity


#### @ClusteringColumn

This annotation indicates which field is a clustering column. Put this annotation on several fields to have many clustering columns. The attribute value indicates the ordering of this clustering column. If you want to store the data in reversed order on disk in SSTables, set the attribute reversed to true

Unlike Java indexes, the ordering starts at 1. It is a design choice since it is more natural for human being to start counting at 1

@Entity
public class MyEntity 
{
	@CompoundPrimaryKey
	private CompoundKey id;
	
	...
		
	private static class CompoundKey 
	{
		@PartitionKey
		private Long userId;

		@ClusteringColumn(value = 1, reversed = true)
		private Date date;
		
		@ClusteringColumn(2)
		private String type;
		...
	}
	...
}

For more detail on this annotation and its usage, please refer to Clustered Entity


#### @Index

This annotation defines a secondary index on a regular column (not columns part of the PRIMARY KEY though, it will be available in Cassandra 2.0)

Additionally you can define the name of the index using the name attribute on the annotation.

Example

@Entity
public class UserEntity {

		@PartitionKey
		private Long id;

		@Column
		@Index(name = "user_name_index")
		private String name;
				
		...
}

#### @Consistency

This annotation can be used on an entity or a Counter field

You need to specify the read and write attribute to define the corresponding consistency level.

Example:

@Entity
@Consistency(read=ConsistencyLevel.ONE,write=ConsistencyLevel.QUORUM)
public class MyBean 
{
	@PartitionKey
	private Long id;
	
	...
		
	@ConsistencyLevel(read=ConsistencyLevel.ONE,write=ConsistencyLevel.ONE)
	@Counter
	@Column
	private Long counter;

}

#### @TimeUUID

This annotation tells Achilles to map a Java UUID field to Cassandra timeuuid type.

Example:

@Entity
@Consistency(read=ConsistencyLevel.ONE,write=ConsistencyLevel.QUORUM)
public class MyBean 
{
	@PartitionKey
	private Long id;
	
  @TimeUUID
  @Column 
  private UUID date;
	...

This is especially useful in CQL to map to timeuuid type so you can use Timeuuid functions like dateOf()/now()/minTimeuuid()/maxTimeuuid() or unixTimestampOf() on native queries


#### @EmptyCollectionIfNull

For collections and maps, Cassandra does not distinguish between empty collection/map and null collection/map.

Therefore if you save an empty list, it is equivalent to setting null to this list, thus deleting it. When reading back the list, Cassandra will return a null value so Achilles will map back to a null list.

To avoid having to check for null, you can add the @EmptyCollectionIfNull annotation on a collection or map, Achilles will then map null value to an empty instance of the collection/map.

Please note that Achilles exposes the same behavior if the javax.validation.constraints.NotNull annotation is set on a collection/map


#### @JSON

Tell Achilles to serialize an un-supported data type into JSON string. Example:

    //Simple value encoding
    @Column
    @JSON
    private MyPOJO pojoAsJson;
    
    //Collection value encoding
    @Column
    @JSON // or @JSON(value=true)
    private List<MyPOJO> myPojosAsJson;
    
    //Map key encoding
    @Column
    @JSON(key=true, value=false)
    private Map<MyPOJO, String> mapKeyEncoding;
        
    //Map value encoding
    @Column
    @JSON // or @JSON(value=true)
    private Map<MyPOJO, String> mapKeyEncoding;
 

#### @TimeUUID

To map a Java UUID type to Cassandra timeuuid type, you need to add the @TimeUUID annotation on the target field.

    @Column
    @TimeUUID
    private UUID date;
    

#### @Strategy

Define the insert and naming strategy on an entity.
For the insert strategy, 2 values are possible: info.archinnov.achilles.type.InsertStrategy.NOT_NULL_FIELDS and info.archinnov.achilles.type.InsertStrategy.ALL_FIELDS

Upon call to insert(), depending on the chosen strategy Achilles will

  1. insert all fields on the entity, even if they are null
  2. insert only non null fields of the entity
@Entity
@Strategy(insert = InsertStrategy.ALL_FIELDS)
public class MyBean 
{
	@PartitionKey
	private Long id;	
	...

Check here for more details on the Insert strategy


For the naming strategy, 3 values are possible: `info.archinnov.achilles.type.NamingStrategy.SNAKE_CASE`, `info.archinnov.achilles.type.NamingStrategy.LOWER_CASE` and `info.archinnov.achilles.type.NamingStrategy.CASE_SENSITIVE`
  • 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

If not set, the strategy defaults to info.archinnov.achilles.type.NamingStrategy.LOWER_CASE

@Entity(keyspace="myKeyspace", table="myTable)
@Strategy(naming = NamingStrategy.SNAKE_CASE)
// final keyspace name will be transformed to 'my_keyspace' and table name to 'my_table'
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
	...
	

Check here for more details on the Naming strategy

@Enumerated

Define the encoding for enum values. This annotation applies on simple field, on value of collections and on key & value of map. The enum exposes 2 attributes:

  1. key: defines the encoding for maps key. Only applies to map types
  2. value: defines the encoding for simple types and values of collections (list/set)

There are 2 defined encoding types: Encoding.NAME and Encoding.ORDINAL, which use respectively the enum name & ordinal for encoding.

Example:

    @Column(name = "pricing")
    @Enumerated(Encoding.ORDINAL)
    private Pricing pricing;
    
    @Column(name = "pricings")
    @Enumerated(Encoding.NAME)
    private List<Pricing> pricings;
    
    @Column(name = "pricing_per_country")
    @Enumerated(key = Encoding.ORDINAL, value = Encoding.NAME)
    private Map<Country, PricingType> pricingPerCountry;    

More details on Enum type


#### @TypeTransformer

Transform a custom Java type into one of native types supported by the Java driver
This annotation defines 2 attributes:

  • keyCodecClass: class of Codec for Map key type transformation. Only useful for Map types
  • valueCodecClass: class of Codec for simple, List and Set type transformation. Please note that in case of List & Set, the transformation applies to the values inside the List/Set, not to the List/Set itself

The Codec class provided should implement the Codec interface.



Let's consider the following codec transforming a Long to a String
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);
    }
}

Example of simple Long type to String type transformation
    @Column
    @TypeTransformer(valueCodecClass = LongToString.class)
    private Long longToString;

Example of List<Long> to List<String> transformation
    @Column
    @TypeTransformer(valueCodecClass = LongToString.class)
    private List<Long> listOfLong;

Example of Set<Long> to Set<String> transformation
    
    @Column
    @TypeTransformer(valueCodecClass = LongToString.class)
    private Set<Long> setOfLong;

Example of key Map transformation: Map<Long,Double> to Map<String,Double>
    @Column
    @TypeTransformer(keyCodecClass = LongToString.class)
    private Map<Long,Double> mapKeyTransformation;

Example of value Map transformation: Map<Integer,Long> to Map<Integer,String>
    @Column
    @TypeTransformer(valueCodecClass = LongToString.class)
    private Map<Integer,Long> mapValueTransformation;

Home

Clone this wiki locally