Wednesday, August 19, 2015

Customizing spring-data-cassandra to support TTL

The REST service I mentioned previously uses Cassandra for data storage and talks to it using Spring Data Cassandra (1.2.2). Integrating with it was easy peasy, all it took is a couple of beans in configuration, and a couple of Repository interfaces (which caused some problems with Spring Boot as I described in this post). But then a new requirement came in, columns in Cassandra had to be inserted with TTL (time to live). CrudRepository methods don't support TTL. In order to support TTL during insertion, save method has to be able to take ttl as a parameter. I wanted to implement this functionality once for all repositories in the application. As described in the documentation2 classes needed to be created:

Interface MyRepository, where I added another save method:
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable> extends TypedIdCassandraRepository<T, ID> {
<S extends T> S save(S s, int ttl);
}
view raw MyRepository hosted with ❤ by GitHub
Class MyRepositoryImpl, where the method was implemented:
@NoRepositoryBean
public class MyRepositoryImpl<lT, ID extends Serializable> extends SimpleCassandraRepository<T, ID> implements MyRepository<T, ID> {
public MyRepositoryImpl(CassandraEntityInformation<T, ID> metadata, CassandraTemplate template) {
super(metadata, template);
this.entityInformation = metadata;
this.template = template;
}
@Override
public <S extends T> S save(S s, int ttl) {
WriteOptions writeOptions=new WriteOptions();
writeOptions.setTtl(ttl);
return template.insert(s, writeOptions);
}
}

Notice that the documentation is for JPA repositories. For Cassandra repository one more step was needed (I did not find the way around it). Reporsitory Factory Bean needed to be created:
public class MyRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends RepositoryFactoryBeanSupport<T, S, ID> {
@Autowired
private CassandraTemplate cassandraTemplate;
@Override
protected RepositoryFactorySupport createRepositoryFactory() {
return new MyRepositoryFactory(cassandraTemplate);
}
public void setCassandraTemplate(CassandraTemplate cassandraTemplate) {
this.cassandraTemplate = cassandraTemplate;
}
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
Assert.notNull(cassandraTemplate, "cassandraTemplate must not be null!");
setMappingContext(cassandraTemplate.getConverter().getMappingContext());
}
public static class MyRepositoryFactory extends CassandraRepositoryFactory {
private final CassandraTemplate cassandraTemplate;
public MyRepositoryFactory(CassandraTemplate cassandraTemplate) {
super(cassandraTemplate);
this.cassandraTemplate = cassandraTemplate;
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getTargetRepository(RepositoryMetadata metadata) {
CassandraEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
return new MyRepositoryImpl(entityInformation, cassandraTemplate);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleRepository.class;
}
}
}
view raw gistfile1.txt hosted with ❤ by GitHub
and it had to be defined in Configuration:
@Configuration
@EnableCassandraRepositories( repositoryFactoryBeanClass = MyRepositoryFactoryBean.class)
view raw Configuration hosted with ❤ by GitHub
Now we can define a Repository, e.g :
public interface SampleRepository extends MyRepository<Sample, String> {
}
Then we just autowire it and call it:
@Autoriwed
SampleRepository sampleRepository;
....
public void useSampleRepository(...) {
....
sampleRepository.save(sample, ttl);
}

6 comments:

  1. Hello

    I tried above code but i am getting following error:
    Caused by: java.lang.IllegalArgumentException: encountered unsupported query parameter type [class java.lang.Object] in method public abstract java.lang.Object com.cisco.operation.CustomTTLRepository.save(java.lang.Object,int)
    at org.springframework.data.cassandra.repository.query.CassandraQueryMethod.verify(CassandraQueryMethod.java:104)
    at org.springframework.data.cassandra.repository.query.CassandraQueryMethod.(CassandraQueryMethod.java:68)

    Please help.

    ReplyDelete
    Replies
    1. The argument for the save method should be class which is annotated with @Table

      Delete
  2. What is sampleRepository.save(sample, ttl);
    in your case?
    Ho do you get sample repository from RepositoryFactorySupport?
    Could you describe in details ?

    ReplyDelete
  3. MyRepositoryFactoryBean is specified in @EnableCassandraRepositories annotations. Spring takes care of initating and creating the repository beans. You just need to autowire them. I updated the post to show how to use SampleRepository.

    ReplyDelete
  4. great article!!!!!This is very importent information for us.I like all content and information.I have read it.You know more about this please visit again.

    Cassandra Training in Chennai

    ReplyDelete