O
Cassandra, o banco de dados não relacional orientados à família de
colunas, é um banco feito para trabalhos de alta performance, alta
escalabilidade horizontal além de ser tolerante a falhas. Uma das
grandes vantagens de sua arquitetura de escalabilidade horizontal é
que se pode fazer com que cresça o número de nós a partir de uma
alta demanda e diminuir em períodos de pouco processamento, ideal
para a plataforma nas nuvens tão comumente discutido. Em sua nova
versão trouxe o novo Cassandra Query Language, CQL, a versão 3.0,
que dentre suas melhorias está no trabalho de coleções, chaves
compostas.
Com
tantas melhorias o único problema é a maneira de tirar proveito de
maneira eficiente desses novos recursos, afinal o foco do
desenvolvedor Java deve ser solução em OO e sobre tudo no objetivo
da aplicação, regras de negócio, a ponto de que esse parser entre
banco e negócio seja o menor possível. Com esse objetivo surgiu o
Easy-Cassandra, seu foco é facilitar o desenvolver Java utilizar
esses recurso sem em nenhum momento desfocar no objetivo e no negócio
da aplicação. Nessa nova versão o framework ORM trouce as
seguintes melhorias:
Chave
Composta
Mapeamento
de coleções( java.util.List, java.util.Set, java.util.Map)
Reconhecimento
automático dos demais cluters na conexão e trabalho de
autobalanceamento.
Melhorias
no levantamento da aplicação.
Criação
do Schema, para o uso de keyspace diferente do passado.
Nessa
nova versão houve algumas quebras de retrocompatibilidades, isso
deve ao uso do java Driver e de suas consultas totalmente focadas no
CQL 3. Essa pequena quebra está apenas no modo em que é criado a
classe ponte entre o cassandra e o seu objeto java já que as
anotações de mapeamento continuam tendo como base as anotações do
JPA 2.0.
Como
a ideia no futuro é que exista o banco MongoDB, foi criada a
interface org.easycassandra.persistence.Persistence, assim os novos
bancos de dados e o Cassandra terão esse contrato, assim será
possível, trocando apenas a implementação, mudando o banco de
dados não relacional.
public interface Persistence {
boolean insert(Object bean);
boolean delete(Object bean);
boolean update(Object bean);
List findAll(Class bean);
T findByKey(Object key, Class bean);
boolean deleteByKey(Object key, Class bean);
boolean executeUpdate(String query);
}
A
interface do cassandra
org.easycassandra.persistence.cassandra.PersistenceCassandra, como
todos os novos bancos compatíveis herda do Persistence e adiciona
alguns pequenos recursos.
public interface PersistenceCassandra extends Persistence {
List findByIndex(Object index, Class bean);
Long count(Class bean);
}
A
classe responsável por gerar a implementação continua sendo a org.easycassandra.persistence.cassandra.EasyCassandraManager, mas ela
se tornou um enum, sim isso mesmo um enum!, já que o objetivo é que
ele seja único nada mais natural que o singleton natural cedido pela
própria JVM. Só é necessário passar o
host e o keyspace, caso não existe ele se responsabilizará por
você. Para adicionar objetos mapeados no Cassandra basta usar o
método addFamilyObject
passando o .class, assim como no caso do keyspace, caso ele não
existe ele se responsabilizará em criar ou alterar a column family,
para o seu objeto.
Como
base nisso o DAO base poderia ficar da seguinte maneira:
public class PersistenceDao {
private static final String KEY_SPACE = "javabahia";
private static final String HOST = "localhost";
private PersistenceCassandra persistence;
private Class baseClass;
public PersistenceDao(Class baseClass) {
this.baseClass = baseClass;
persistence = EasyCassandraManager.INSTANCE.getPersistence(HOST, KEY_SPACE);
EasyCassandraManager.INSTANCE.addFamilyObject(baseClass, KEY_SPACE);
}
public boolean insert(T bean) {
return persistence.insert(bean);
}
public boolean remove(T bean) {
return persistence.delete(bean);
}
public boolean removeFromRowKey(K rowKey) {
return persistence.deleteByKey(rowKey, baseClass);
}
public boolean update(T bean) {
return persistence.update(bean);
}
public T retrieve(Object id) {
return persistence.findByKey(id, baseClass);
}
public List listAll() {
return persistence.findAll(baseClass);
}
public List listByIndex(Object index) {
return persistence.findByIndex(index, baseClass);
}
public Long count() {
return persistence.count(baseClass);
}
public boolean executeUpdateCql(String string) {
return persistence.executeUpdate(string);
}
}
Uma
outra estratégia seria a mudança de interface para a persistence,
assim esse DAO seria para todos os bancos de dados nosql que o
framework for sendo compatível. Uma vez pronto o nosso projeto base,
vamos falar um pouco sobre o mapeamento dos objetos. Os
desenvolvedores java que conhecem o JPA já estarão familiarizado
como o modelo de mapeamento.
@Entity(name = "person")
public class Person implements Serializable {
private static final long serialVersionUID = 3L;
@Id
private Long id;
@Index
@Column(name = "name")
private String name;
@Column(name = "born")
private Integer year;
@Enumerated
private Sex sex;
@Embedded
private Address address;
//getter and setter
}
Assim
os campos são:
@Id
serve para indicar que esse campo será a chave da linha.
@Enumerated
indica que o campo é um enum.
@Embedded
indica que existe um objeto complexo e este será embitudio na mesma
linha.
@Index
indica o índice, funciona como um segundo ID, assim será possível
realizar busca a partir desse campo.
@Table
ou @Entity indica que o objeto é mapeado e pega o nome da column
family
@EmbeddedId
indica que esse campo é um objeto complexo e nele está embutido as
chaves da column family.
@Entity(name="linux")
public class LinuxDistribuition {
@EmbeddedId
private IdLinux id;
@Column
private String guy;
@Column
private String version;
@Column(name="descriptions")
private String descriptions;
//getter and setter
}
public class IdLinux {
@Column
private String name;
@Column
private String kernelVersion;
//getter and setter
}
Além
desses campos essa nova versão trouxe novas anotações para se
poder trabalhar com listas dentro do cassandra que são:
- SetData
indica que o campo é uma interface java.util.Set, é necessário
informar o classeData na qual indica o tipo do objeto que terá no
set.
- ListData
indica que o campo é uma interface java.util.List, é necessário
informar o classeData na qual indica o tipo do objeto que terá na
lista.
- MapData
indica que o campo é uma interface java.util.Map, é necessário
informar a chave e o valor do map informando o classkey e classValue
respectivamente.
Exemplos
do uso dessa interação abaixo:
@Entity(name="resumebook")
public class Book {
@Id
@Column(name="booksname")
private String name;
@MapData(classKey=Long.class,classValue=String.class)
private Map chapterResume;
//getter and setter
}
@Entity(name="shopping")
public class ShoppingList {
@Id
private String name;
@Column(name="day")
private Date day;
@ListData(classData=String.class)
@Column(name="frutis")
private List fruits;
@Column(name="storeName")
private String storeName;
//getter and setter
}
@Entity(name="contact")
public class Contact implements Serializable {
@Id
@Column(name="id")
private String name;
@Column(name="emails")
@SetData(classData=String.class)
private Set emails;
//getter and setter
}
Uma
vez mapeado e com o DAO pronto o seu uso se torna muito fácil, já
que as buscas são semelhantes a única diferença está no objeto
mapeado e o retorno.
public class BookDAOTest {
private PersistenceDao persistence=new PersistenceDao(Book.class);
@Test
public void insertTest() {
Book book = getBook();
Assert.assertTrue(persistence.insert(book));
}
private Book getBook() {
Book book = new Book();
book.setName("Cassandra Guide ");
Map resumeChapter=new HashMap();
resumeChapter.put(1l, "this chapter describes new resources in cassandra and improvements with CQL");
resumeChapter.put(2l, "Understanding the architecture");
resumeChapter.put(3l, "Installing DataStax Community");
resumeChapter.put(4l, "Upgrading Cassandra");
resumeChapter.put(5l, "Initializing a cluster");
resumeChapter.put(6l, "Security");
resumeChapter.put(7l, "Database design");
resumeChapter.put(8l, "Using the database");
resumeChapter.put(9l, "Database internals");
resumeChapter.put(10l, "Configuration");
resumeChapter.put(11l, "Operations");
resumeChapter.put(12l, "Backing up and restoring data");
resumeChapter.put(13l, "Cassandra tools");
resumeChapter.put(14l, "Troubleshooting");
resumeChapter.put(14l, "Troubleshooting");
resumeChapter.put(15l, "References");
book.setChapterResume(resumeChapter);
return book;
}
@Test
public void retrieveTest() {
Book book=persistence.retrieve(getBook().getName());
Assert.assertNotNull(book);
}
@Test
public void removeTest() {
persistence.remove(getBook());
Assert.assertNull(persistence.retrieve(getBook().getName()));
persistence.insert(getBook());
}
}
public class ContactsDAOTest {
private PersistenceDao persistence=new PersistenceDao(Contact.class);
@Test
public void insertTest() {
Contact contacts = getContact();
Assert.assertTrue(persistence.insert(contacts));
}
private Contact getContact() {
Contact contacts = new Contact();
contacts.setName("Shrek ");
contacts.setCyte("far far away");
contacts.setEmails(new HashSet());
contacts.getEmails().add("shreck@shrek.org");
contacts.getEmails().add("shreck@farfaraway.far");
return contacts;
}
@Test
public void retrieveTest() {
Contact contact=persistence.retrieve(getContact().getName());
Assert.assertNotNull(contact);
}
@Test
public void removeTest() {
persistence.remove(getContact());
Assert.assertNull(persistence.retrieve(getContact().getName()));
}
}
public class ShoppingListDAOTest {
private PersistenceDao persistence=new PersistenceDao(ShoppingList.class);
@Test
public void insertTest() {
Assert.assertTrue(persistence.insert(getPolianaShoppingList()));
}
private ShoppingList getPolianaShoppingList() {
ShoppingList shopping=new ShoppingList();
shopping.setDay(new Date());
shopping.setName("Poliana");
shopping.setFruits(new LinkedList());
shopping.getFruits().add("Orange");
shopping.getFruits().add("beans");
shopping.getFruits().add("rice");
return shopping;
}
@Test
public void retrieveTest() {
ShoppingList list=persistence.retrieve(getPolianaShoppingList().getName());
Assert.assertNotNull(list);
}
@Test
public void removeTest() {
ShoppingList list=persistence.retrieve(getPolianaShoppingList().getName());
persistence.remove(list);
list=persistence.retrieve(getPolianaShoppingList().getName());
Assert.assertNull(list);
}
}
Outro
recuso importante é que foi solicitado
pela comunidade, abraços ao pessoal da Bern
na Suiça, é a
possibilidade de usar outro keyspace diferente do definido no
persistence. Para isso basta usar o schema na anotação @Table como
mostra o exemplo abaixo:
@Table(name="drink",schema="schemaA")
public class Drink implements Serializable {
@Id
private Long id;
@Index
@Column(name = "name")
private String name;
@Column(name = "flavor")
private String flavor;
A
nova versão do Easy-Cassandra, 2.0.0, ainda está em fase beta,
assim é necessário realizar mais testes, mas com isso tem noção
do que vem está por vir. Uma das coisas legais do framework é
pioneirismos do projeto, até então ele é o primeiro ORM do
cassandra que utiliza os recursos do 100% CQL 3.0. Isso graças ao
grande trabalho por parte da comunidade.