Using the mapper
First, create a MappingManager. It wraps an existing Session instance:
MappingManager manager = new MappingManager(session);
MappingManager is thread-safe and can be safely shared throughout your
application. You would typically create one instance at startup, right
after your Session.
Note that MappingManager will initialize the Session if not
previously done (this was not the case in previous driver versions; if
this is a problem for you, see MappingManager(session, protocolVersion)).
Entity mappers
Each entity class (annotated with @Table) is managed by a dedicated
Mapper object. You obtain this object from the MappingManager:
Mapper<User> mapper = manager.mapper(User.class);
Mapper objects are thread-safe. The manager caches them internally, so
calling manager#mapper more than once for the same class will return
the previously generated mapper.
Basic CRUD operations
To save an object, use Mapper#save:
UUID userId = ...;
User u = new User(userId, "John Doe", new Address("street", 01000));
mapper.save(u);
To retrieve an object, use Mapper#get:
UUID userId = ...;
User u = mapper.get(userId);
get‘s arguments must match the partition key components (number of
arguments and their types).
To delete a row in a table, use Mapper#delete. This method support
deleting a row given either its primary keys, or the object to delete:
UUID userId = ...;
mapper.delete(userId);
mapper.delete(u);
All these CRUD operations are synchronous, but the Mapper provides
their asynchronous equivalents:
ListenableFuture<Void> saveFuture = mapper.saveAsync(u);
ListenableFuture<User> userFuture = mapper.getAsync(userId);
ListenableFuture<Void> deleteFuture = mapper.deleteAsync(userId);
Mapper options
The basic CRUD operations accept additional options to customize the underlying query:
-
ttl: add a time-to-live value for the operation. -
timestamp: add a timestamp value for the operation. -
consistencyLevel: specify a consistency level. -
tracing: set tracing flag for the query. -
saveNullFields: if set to true, fields with valuenullin an instance that is to be persisted will be explicitly written asnullin the query. If set to false, fields with null value won’t be included in the write query (thus avoiding tombstones), or if using Protocol V4+ unset() will be used for null values. If not specified, the default behavior is to persistnullfields. -
ifNotExists: if set to true, adds anIF NOT EXISTSclause to the save operation (useifNotExists(false)if you enabled the option by default and need to disable it for a specific operation).
To use options, add them to the mapper call after regular parameters:
import static com.datastax.driver.mapping.Mapper.Option.*;
mapper.save(new User(userId, "helloworld"),
timestamp(123456L), tracing(true), ttl(42));
Some options don’t apply to all operations:
| Option | save/saveQuery | get/getQuery | delete/deleteQuery |
| Ttl | yes | no | no |
| Timestamp | yes | no | yes |
| ConsistencyLevel | yes | yes | yes |
| Tracing | yes | yes | yes |
| SaveNullFields | yes | no | no |
| IfNotExists | yes | no | no |
Note that Option.consistencyLevel is redundant with the consistency
level defined by @Table.
If both are defined, the option will take precedence over the
annotation.
Default options can be defined for each type of operation:
mapper.setDefaultGetOption(tracing(true), consistencyLevel(QUORUM));
mapper.setDefaultSaveOption(saveNullFields(false));
mapper.setDefaultDeleteOption(consistencyLevel(ONE));
// Given the defaults above, this will use tracing(true), consistencyLevel(ONE)
mapper.get(uuid, consistencyLevel(ONE));
To reset default options, use the following methods:
mapper.resetDefaultGetOption();
mapper.resetDefaultSaveOption();
mapper.resetDefaultDeleteOption();
Access to underlying Statements
Instead of performing an operation directly, it’s possible to ask the
Mapper to just return the corresponding Statement object. This gives
the client a chance to customize the statement before executing it.
-
Mapper.saveQuery(entity): returns a statement generated by the mapper to saveentityinto the database. -
Mapper.getQuery(userId): returns a statement to select a row in the database, selected on the givenuserId, and matching the mapped object structure. -
Mapper.deleteQuery(userID): returns a statement to delete a row in the database given theuserIdprovided. This method can also accept a mapped object instance.
Manual mapping
Mapper#map provides a way to convert the results of a regular query:
ResultSet results = session.execute("SELECT * FROM user");
Result<User> users = mapper.map(results);
for (User u : users) {
System.out.println("User : " + u.getUserId());
}
This method will ignore:
- extra columns in the
ResultSetthat are not mapped for this entity. - mapped fields that are not present in the
ResultSet(setters won’t be called so the value will be the one after invocation of the class’s default constructor).
Result is similar to ResultSet but for a given mapped class.
It provides methods one(), all(), iterator(), getExecutionInfo()
and isExhausted(). Note that iterating the Result will consume the
ResultSet, and vice-versa.
Accessors
Accessors provide a way to map custom queries not supported by the
default entity mappers.
To create an accessor, define a Java interface and annotate each method to provide the corresponding CQL query:
@Accessor
public interface UserAccessor {
@Query("SELECT * FROM user")
Result<User> getAll();
}
The MappingManager can then process this interface and automatically generate an
implementation for it:
UserAccessor userAccessor = manager.createAccessor(UserAccessor.class);
User user = userAccessor.getOne(uuid);
Like mappers, accessors are cached at the manager level and thus, are thread-safe/sharable.
Parameters
A query can have bind markers, that will be set with the method’s arguments.
With unnamed markers, the order of the arguments must match the order of the markers:
@Query("insert into user (id, name) values (?, ?)")
ResultSet insert(UUID userId, String name);
With named markers, use @Param to indicate which parameter corresponds to which marker:
@Query("insert into user (userId, name) values (:u, :n)")
ResultSet insert(@Param("u") UUID userId, @Param("n") String name);
If a method argument is a Java enumeration, it must be annotated with
@Enumerated to indicate how to convert it to a CQL type (the rules are
the same as in mapping definition):
@Query("insert into user (key, gender) values (?,?)")
ResultSet addUser(int key, @Enumerated(EnumType.ORDINAL) Enum value);
Return type
The declared return type of each method affects how the query will get executed:
| Return type | Effect |
void |
Synchronous execution, discards the results of the query. |
ResultSet |
Synchronous execution, returns unmapped results. |
T |
T must be a mapped class.Synchronous execution, returns the first row (or null if there are no results). |
Result<T> |
T must be a mapped class.Synchronous execution, returns a list of mapped objects. |
ResultSetFuture |
Asynchronous execution, returns unmapped results. |
ListenableFuture<T> |
T must be a mapped class.Asynchronous execution, returns the first row (or null if there are no results). |
ListenableFuture<Result<T>> |
T must be a mapped class.Asynchronous execution, returns a list of mapped objects. |
Example:
@Query("SELECT * FROM user")
public ListenableFuture<Result<User>> getAllAsync();
Customizing the statement
It is possible to customize query parameters to include in a Accessor
query with the annotation @QueryParameters. Then, options like
consistency level, fetchsize or tracing are settable:
@Query("SELECT * FROM ks.users")
@QueryParameters(consistency="QUORUM")
public ListenableFuture<Result<User>> getAllAsync();
Mapping configuration
MappingConfiguration lets you configure low-level aspects of the object mapper. It is configured when initializing the mapping manager:
PropertyMapper propertyMapper = ... ; // see examples below
MappingConfiguration configuration =
MappingConfiguration.builder()
.withPropertyMapper(propertyMapper)
.build();
MappingManager manager = new MappingManager(session, configuration);
The main component in the configuration is PropertyMapper, which controls how annotated classes will relate to database objects. The best way to plug in specific behavior is to create an instance of DefaultPropertyMapper and customize it.
For example, the mapper’s default behavior is to try to map all the properties of your Java objects.
You might want to take the opposite approach and only map the ones that are specifically annotated
with @Column or @Field:
PropertyMapper propertyMapper = new DefaultPropertyMapper()
.setPropertyTransienceStrategy(PropertyTransienceStrategy.OPT_IN);
Another common need is to customize the way Cassandra column names are inferred. Out of the box, Java
property names are simply lowercased, so a userName property would be mapped to the username column.
To map to user_name instead, use the following:
PropertyMapper propertyMapper = new DefaultPropertyMapper()
.setNamingStrategy(new DefaultNamingStrategy(
NamingConventions.LOWER_CAMEL_CASE,
NamingConventions.LOWER_SNAKE_CASE));
There is more to DefaultPropertyMapper; see the Javadocs and implementation for details.