Escolar Documentos
Profissional Documentos
Cultura Documentos
Introdução ao Cassandra
A linguagem CQL
Nesse caso, foi criada a nova coluna utilizando o tipo LIST<text>, com
isso, estamos informando uma coluna que é uma lista ordenada de
valores do tipo texto. Outros tipos de coleção disponíveis são SET (lista
não ordenada) e MAP (lista de chave-valor). Não é possível alterar a
coluna phone_number, que é do tipo texto, para um
tipo LIST<text>. O Cassandra não permite esse tipo de operação,
então o comando seguinte exclui essa coluna da tabela.
Uma coleção pode guardar até 64.000 elementos dentro dela, porém, é
importante salientar que elas são projetadas para guardar pequenos
conjuntos de dados, coleções muito extensas e com elementos muito
complexos impactam a performance de leitura dos dados. Em um
cenário desse tipo é mais indicado criar uma tabela à parte.
Ao definir uma coluna com esse tipo, é garantido que o campo não
terá nenhuma chave a mais do que as que estão descritas
(street, city, state e zip_code), contudo, não é obrigatório preencher
todos os valores.
Podemos ver que para adicionar a nova coluna foi necessário definir o
tipo address como FROZEN, sem isso, o Cassandra não vai permitir
que a coluna seja criada. Isso se deve ao fato do Cassandra não
suportar por completo coleções aninhadas (tipos customizados são
tratados como coleções) devido à forma como os dados da coluna são
serializados. Ao definir uma coleção como FROZEN não é possível
alterar atributos individuais dela (como alterar somente o valor do
atributo street da coleção address de uma linha já existente da
tabela), é necessário substituir a coleção por completo. Após criar a
nova coluna, inserimos um novo endereço com a chave ‘home’ para
um dos contatos. Por fim, podemos ver o resultado com o comando
SELECT.
TL (Time to Live)
Níveis de consistência
BOX 1. HINT
tentar executar uma operação de escrita nele, funcionando como uma espécie
caso bem-sucedido.
e escrita
CONSISTENCY;
Current consistency level is ONE.
CONSISTENCY LOCAL_ONE;
Consistency level set to LOCAL_ONE.
Em muitos casos essa exposição se deu pelo fato dos sistemas terem
apresentado um crescimento bastante elevado do número de acessos,
o que culminou em um aumento exponencial do volume de dados a tal
ponto que os bancos relacionais passaram a ter dificuldades em
processar as requisições com um tempo de resposta satisfatório.
Como era de se esperar, o Cassandra não foi escolhido por acaso. Ele é
altamente escalável, possui uma arquitetura P2P tolerante a falhas,
um modelo de dados versátil e flexível e uma linguagem de consulta
com baixa curva de aprendizado. Todas essas características fazem
com que o Cassandra seja o repositório perfeito para aplicações que
precisam estar sempre disponíveis e que operam com grandes
volumes de escrita e leitura de dados. Com esta solução NoSQL é
possível atender a milhões de transações por segundo, em grandes
volumes de dados, fazendo uso de milhares de servidores.
Uma tabela com partições com múltiplas linhas tem a chave primária
composta pela coluna que representa a chave da partição e pela
coluna que representa a chave de ordenação. Essa figura, em conjunto
com a Tabela 2, ilustram exatamente esse conceito, onde a chave da
partição é a coluna year e a chave de ordenação/identificador da linha
é a coluna name. Dessa forma, se uma pesquisa filtrar por year = 2014,
consultará um único nó do cluster, o qual contém a partição cujo
identificador é 2014, retornando duas linhas.
Modelo Conceitual
Para fazer a modelagem conceitual será utilizada a técnica de
modelagem entidade-relacionamento (ERM – Entity-Relationship
Model), a qual visa construir um modelo que descreve dados,
informações, domínios de negócio e até mesmo requisitos da
aplicação de forma abstrata. Os principais componentes desse modelo
são as entidades e os relacionamentos que existem entre elas.
se separada de outra. Nestes casos temos uma "entidade fraca", por exemplo:
seguir:
Fluxo da aplicação
Com o modelo conceitual preparado é preciso definir o fluxo de
funcionamento da aplicação, de forma a identificar os pontos de
otimização do modelo a fim de atender às requisições feitas à base de
dados.
Deste modo, com uma única consulta é possível obter resultados mais
completos, que podem servir para mais de uma tarefa do fluxo da
aplicação, diminuindo assim a quantidade de tabelas e consultas
necessárias no banco de dados. Por exemplo, uma mesma consulta
poderia servir para as tarefas “Usuário loga na aplicação” e “Mostrar
vídeos adicionados pelo usuário”, bastando encadear as entidades
Usuário e Vídeo numa mesma tabela.
Regras de mapeamento
As regras de mapeamento são orientações a serem seguidas a fim de
mapear as entidades do modelo conceitual para o modelo lógico.
Nota: Saiba que as regras de mapeamento não são passos para a construção de
mesmo para criar um modelo lógico sem relações entre entidades, no qual
Além disso, saiba que todo relacionamento entre duas entidades tem o
potencial de gerar duas tabelas, cada uma representando uma direção
do relacionamento, uma vez que todo relacionamento é, por natureza,
bidirecional. Logo, como demonstrado na Figura 13, o
relacionamento 1-N entre as entidades Usuario e Video pode (ou não, a
depender da necessidade do negócio) gerar as
tabelas videos_do_usuario, a qual tem o intuito de listar os vídeos de
determinado usuário, obtendo os usuários com seus respectivos
vídeos, e usuarios_do_video, que, por sua vez, tem a intenção oposta,
de listar os usuários de um determinado vídeo.
Nota: Nada impede que se escolha colocar a coluna usuario_id como chave de
é suficiente.
trata. Portanto, a escolha de qual coluna vai ser que tipo de chave é meramente
objetiva, levando em conta apenas os tipos de consultas que a tabela deve
suportar.
Este artigo teve como principal objetivo prover uma visão geral da
modelagem de dados para o banco de dados Apache Cassandra,
através de uma abordagem introdutória. Dessa forma, ainda existe
muito assunto a ser explorado e que pode ser o objeto de estudo de
outros artigos no futuro. O importante, neste momento, é entender os
conceitos básicos e começar a colocá-los em prática em seus projetos.
Por fim, saiba que o Cassandra pode fazer muito mais do que o
demonstrado aqui, sendo, portanto, de suma importância que os
leitores consultem a bibliografia disposta na seção Links. Dito isso,
explorem esse banco de dados, conheçam sua arquitetura,
frameworks relacionados, configurações avançadas e também casos
de uso, pois é importante saber quando e como fazer uso desta
diferenciada opção.
Links
http://cassandra.apache.org/
http://www.datastax.com/
http://docs.datastax.com/en/cql/3.1/cql/cql_reference/cqlReferenceTOC.html
Site oficial da ferramenta KDM, para automação da modelagem de dados do
Cassandra.
http://kdm.dataview.org/
http://www.cs.wayne.edu/andrey/papers/TR-BIGDATA-05-2015-CKL.pdf
Thrift API, uma interface baseada no protocolo RPC e que era bastante
Thrift API estavam presentes. Somente com o CQL3 o Cassandra pôde ter uma
Acessando o CQL
A maneira mais comum de acessar o CQL é através da
ferramenta cqlsh, como pode ser observado na Figura 2. Trata-se de
um cliente de linha de comando que vem junto com a instalação do
Cassandra (CASSANDRA_HOME/bin/cqlsh).
Figura 2. Executando comandos CQL através do cqlsh.
Partition Key
Todas as tabelas do Cassandra precisam definir uma chave
denominada Partition Key. Esta tem como principal utilidade
determinar em qual nó do cluster um dado será armazenado e trata-se
de um conceito fundamental a todos que lidam com essa base de
dados.
Neste ponto vale ressaltar que não se deve confundir partition key
com primary key. Por exemplo, digamos que uma tabela definiu sua
chave primária da seguinte forma:
Clustering Column
Outro importante aspecto do Cassandra são as Clustering Columns.
Essas colunas fazem parte da primary key, mas não da partition key.
No exemplo apresentado anteriormente, as
colunas status e book_isbn seriam as clustering columns.
gera tokens de 64 bits que englobam um range de -263 a 263-1. Para mais
seção Links).
propriedades:
dados;
que ele seja dividido por quebras na comunicação da rede. Por exemplo, um
cluster composto de dois data centers deverá funcionar mesmo que esses data
Tunable Consistency
Consistência tunável nada mais é do que a capacidade de configurar o
grau de consistência desejado, seja num nível mais global ou em nível
de operação (select, insert, delete, update). Dessa forma, numa
operação mais crítica para a aplicação o desenvolvedor pode setar a
consistência para um nível forte, e numa operação menos importante,
pode usar consistência fraca. Isso faz com que o Cassandra haja tanto
como um sistema AP (Availability e Partition Tolerant) quanto como um
sistema CP (Consistency e Partition Tolerant).
Para cada coluna de uma tabela no Cassandra, além do seu valor também é
Assim, caso haja uma atualização concorrente numa coluna, a mais recente é a
que prevalecerá. Essa é uma técnica de resolução de conflito conhecida
como Last Write Wins. É através desse timestamp também que o Cassandra
para retorná-la nas consultas, bem como para disparar um read repair aos nós
desatualizados.
Lightweight Transactions
Em bancos de dados relacionais uma arquitetura master-slave é uma
estratégia comum para se implementar um cluster. Por outro lado, o
Cassandra usa uma arquitetura do tipo peer-to-peer, na qual qualquer
nó do cluster tem o mesmo papel, ou seja, todos podem receber
escritas e leituras.
Cassandra na prática
Agora que você tem uma visão geral do que é o Cassandra e como
funcionam vários aspectos importantes da arquitetura, vamos ao que
interessa: a parte prática!
Montando o ambiente
O tutorial foi desenvolvido utilizando o Eclipse Mars, mas você pode
optar por qualquer IDE que achar melhor. Com a IDE definida, para
colocar o Cassandra para rodar basta fazer o download, descompactar
o arquivo, entrar na pasta bin e executar o comando cassandra -f para
Linux ou cassandra.bat -f para Windows.
Configurando o projeto
Neste tópico iremos preparar a maior parte das configurações da
aplicação de modo a nos concentrar posteriormente no código que
mais interessa a este artigo. Portanto, vamos às configurações.
Configurando os poms
O pom do projeto principal, demonstrado na Listagem 1, terá as
configurações que serão compartilhadas entre os dois módulos, por
exemplo, dependências comuns ao módulo business e ao módulo
web, como é o caso do CDI e Hibernate Validator. Além disso, para
garantir que utilizaremos as mesmas APIs e versões contidas no
WildFly, adicionamos uma dependência ao BOM (Bill of Materials) do
WildFly 9. Assim você só precisa declarar as APIs do Java EE 7 que irá
usar, sem se preocupar com versões ou escopo, pois estas
configurações já estão definidas no BOM.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>br.com.devmedia</groupId>
<artifactId>webshelf</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>webshelf-business</module>
<module>webshelf-web</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<version.jboss.bom>9.0.1.Final</version.jboss.bom>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.wildfly.bom</groupId>
<artifactId>jboss-javaee-7.0-wildfly</artifactId>
<version>${version.jboss.bom}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
</project>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.devmedia</groupId>
<artifactId>webshelf</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>webshelf-business</artifactId>
<packaging>ejb</packaging>
<build>
<plugins>
<plugin>
<artifactId>maven-ejb-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<ejbVersion>3.2</ejbVersion>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
</dependency>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>br.com.devmedia</groupId>
<artifactId>webshelf</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>webshelf-web</artifactId>
<packaging>war</packaging>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<warName>webshelf</warName>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>prime-repo</id>
<name>PrimeFaces Maven Repository</name>
<url>http://repository.primefaces.org</url>
<layout>default</layout>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>br.com.devmedia</groupId>
<artifactId>webshelf-business</artifactId>
<version>1.0.0</version>
<type>ejb</type>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.servlet</groupId>
<artifactId>jboss-servlet-api_3.1_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.faces</groupId>
<artifactId>jboss-jsf-api_2.2_spec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-jsf-module-api</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.deltaspike.modules</groupId>
<artifactId>deltaspike-jsf-module-impl</artifactId>
<version>1.5.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.primefaces</groupId>
<artifactId>primefaces</artifactId>
<version>5.3</version>
</dependency>
<dependency>
<groupId>org.primefaces.themes</groupId>
<artifactId>blitzer</artifactId>
<version>1.0.10</version>
</dependency>
</dependencies>
</project>
Ativando o CDI
Para que seja possível utilizar o CDI no projeto, será necessário criar o
arquivo beans.xml nos dois módulos. No módulo business, o arquivo
deve ficar localizado na pasta src/main/resources/META-INF, enquanto
no módulo web deve ficar na pasta src/main/webapp/WEB-INF.
A Listagem 4 apresenta o conteúdo desse arquivo, que deve ser o
mesmo nos dois módulos.
Preparando o web.xml
Como se trata de uma aplicação web, não poderia faltar o web.xml.
Esse arquivo deve ficar no módulo web em src/main/webapp/WEB-
INF e é apresentado na Listagem 5. Nele temos algumas configurações
que merecem atenção. A primeira delas é o mapeamento do servlet
JSF (o Faces Servlet). O intuito dessa configuração é mapear o servlet
para qualquer URL terminada com *.xhtml, que é a extensão usada
nas páginas JSF do projeto.
Outra configuração é a definição do tema do PrimeFaces através do
parâmetro primefaces.THEME. No nosso exemplo optamos por
utilizar o blitzer. Caso você prefira outro, basta alterar esse parâmetro
com o nome do tema escolhido e incluir a dependência no webshelf-
web/pom.xml. Para saber os temas que o PrimeFaces disponibiliza,
basta acessar PrimeFaces Web Site (Links).
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<context-param>
<param-name>primefaces.THEME</param-name>
<param-value>blitzer</param-value>
</context-param>
<context-param>
<param-name>primefaces.CLIENT_SIDE_VALIDATION</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-
name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
<param-value>true</param-value>
</context-param>
<p align="left"></web-app>
package br.com.devmedia.webshelf.data;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.Session;
import com.datastax.driver.mapping.MappingManager;
@Singleton
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class CassandraCluster {
@PostConstruct
private void init() {
cluster = Cluster.builder().addContactPoint("localhost").build();
session = cluster.connect();
mappingManager = new MappingManager(session);
}
@PreDestroy
private void destroy() {
session.close();
cluster.close();
}
}
● Deve haver apenas uma instância da classe Cluster para cada cluster
que sua aplicação precisar conectar. Essa instância precisará existir
durante todo o ciclo de vida da aplicação;
>
Cadastro de Usuário
A primeira funcionalidade do projeto WebShelf será o Cadastro de
Usuário, o qual permitirá que qualquer pessoa se inscreva no site para
usufruir de seus serviços.
Esta funcionalidade consistirá de uma tela simples, feita em JSF 2.2
com apoio do PrimeFaces 5.3. A página usará alguns recursos dessa
biblioteca para prover melhor responsividade de seus componentes.
Vale ressaltar que alguns desses recursos de responsividade podem
ser usados desde a versão 5.1, quando o time do PrimeFaces
aumentou os esforços para melhor gradativamente essa característica
da biblioteca.
Essa tela irá invocar um método no controller, que por sua vez irá
delegar para um método de negócio o qual será responsável por fazer
algumas validações e então inserir o registro no Cassandra.
package br.com.devmedia.webshelf.model;
import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotBlank;
import com.datastax.driver.mapping.annotations.PartitionKey;
import com.datastax.driver.mapping.annotations.Table;
@Table(keyspace = "webshelf", name = "user")
public class User implements Serializable {
@PartitionKey
@NotBlank(message="Login: não pode está em branco.")
private String login;
//constructors
<h:head>
<title>WebShelf</title>
<meta name="viewport" content="user-scalable=no, width=device-width,
initial-scale=1.0, maximum-scale=1.0"/>
</h:head>
<h:body>
<f:view encoding="UTF-8" contentType="text/html" locale="pt_BR">
<p:layout fullPage="true">
<p:layoutUnit id="layoutWest" position="west" header="Menu"
collapsible="true" collapsed="true" rendered="#{renderedMenuBar}">
<h:form prependId="false">
<p:menubar>
<p:menuitem value="Home" outcome="home"/>
<p:menuitem value="Logout" action="#{loginController.logout}" />
</p:menubar>
</h:form>
</p:layoutUnit>
Links
NoSQL Distilled.
http://martinfowler.com/books/nosql.html
http://docs.datastax.com/en/cassandra/2.2/index.html
http://www.datastax.com/dev/blog
Planet Cassandra.
http://www.planetcassandra.org/
http://pt.slideshare.net/doanduyhai/cassandra-introduction-at-finishjug
https://github.com/allegro/cassandra-modeling-kata
http://www.primefaces.org
Parte II
Veja abaixo a segunda parte do artigo - Agora as partes I e II foram
compiladas em um único artigo. Bons estudos :)
Por que eu devo ler este artigo:Veremos neste artigo uma visão prática da
utilização do Apache Cassandra seguindo as melhores recomendações do
mercado. Assim, para quem conhece o Cassandra num nível apenas teórico,
poderá aqui “colocar a mão na massa” e dar os primeiros passos neste que é um
dos bancos de dados NoSQL mais empregados.
Ademais, tudo é feito dentro do contexto de uma aplicação Java EE, demonstrando
como essas duas tecnologias podem ser integradas. A aplicação de exemplo
utilizará o driver da DataStax para se comunicar com o Cassandra, WildFly 9,
PrimeFaces 5.3, Cassandra 2.2, além de outras tecnologias.
Evoluindo o WebShelf
Na primeira parte do artigo iniciamos o desenvolvimento do
WebShelf: uma aplicação que possibilita aos seus usuários manter
uma prateleira de livros online ao estilo do Amazon Shelfari. Até o
momento, as principais configurações do projeto já foram
demonstradas e explicadas, o que nos possibilita agora focar mais nas
funcionalidades da aplicação.
<ui:define name="mainContent">
<h:form prependId="false">
<div class="ui-fluid">
<p:panelGrid columns="3"
layout="grid"
columnClasses="ui-
grid-col-1,ui-grid-col-4,ui-grid-col-7"
styleClass="ui-
panelgrid-blank">
<p:outputLabel
for="userName" value="Nome" />
<p:inputText
id="userName" value="#{userController.user.name}" />
<p:message
for="userName" />
<p:outputLabel
for="userLogin" value="Login" />
<p:inputText
id="userLogin" autocomplete="false"
value="#{userController.user.login}" />
<p:message
for="userLogin" />
<p:outputLabel
for="userPassword" value="Senha" />
<p:password
id="userPassword" autocomplete="false"
match="userPasswordConfirmation"
value="#{userController.password}" />
<p:message
for="userPassword" />
<p:outputLabel
for="userPasswordConfirmation"
value="#{userController.password}" />
<p:message
for="userPasswordConfirmation" />
<p:commandButton
value="Salvar" action="#{userController.insert}"
</p:panelGrid>
</div>
</h:form>
</ui:define>
</ui:composition>
package br.com.devmedia.webshelf.controller;
import java.io.Serializable;
import javax.faces.view.ViewScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.deltaspike.jsf.api.message.JsfMessage;
import org.hibernate.validator.constraints.NotBlank;
import br.com.devmedia.webshelf.model.User;
import br.com.devmedia.webshelf.service.UserBean;
import br.com.devmedia.webshelf.util.Messages;
@Named
@ViewScoped
public class UserController implements Serializable {
@Inject
private UserBean userBean;
@Inject
private JsfMessage<Messages> messages;
package br.com.devmedia.webshelf.util;
import org.apache.deltaspike.core.api.message.MessageBundle;
import org.apache.deltaspike.core.api.message.MessageTemplate;
@MessageBundle
public interface Messages {
Implementando o cache de
PreparedStatements em CassandraCluster
Como dito na seção “Regras de utilização do driver DataStax”, se você
perceber que irá executar um statement de forma repetida,
recomenda-se que essas instruções sejam feitas através
de PreparedStatement. Além disso, as instâncias dessa classe
precisam ser mantidas num cache para evitar que o mesmo CQL seja
preparado mais de uma vez, o que pode gerar problemas de
performance.
return preparedStatementCache.get(cql).bind();
}
Por fim, saiba que o método prepare() sempre retornará uma nova
instância de BoundStatement, a qual será utilizada pelos clientes para
fazer o bind dos parâmetros contidos na instrução CQL.
BOX 1. Caches
que melhor se adequa à sua realidade. Apesar de ser possível utilizar maps
para esse intuito, como fizemos aqui, esta não é a única maneira e,
exemplo, você pode precisar de um cache que expira itens (mais antigos,
menos utilizados, etc.) ou caso contrário irá acumular uma grande quantidade
@Lock(LockType.READ)
public ResultSet execute(Statement stmt){
return session.execute(stmt);
}
Assim, um cliente terá que esperar o outro terminar para que então
seu pedido seja atendido. Por exemplo, suponha que a
thread A chamou execute() passando um statement que vai demorar
cinco minutos para finalizar sua execução. Quando a chamada
de A estava com 1 minuto de execução, a thread B também
invocou execute() com outro statement, que nesse caso irá levar
apenas 1 segundo para executar. No entanto, como A iniciou a
execução primeiro, a thread B só será atendida quando A terminar, ou
seja, o comando de B que deveria levar apenas 1 segundo irá levar 4
minutos (tempo restante para completar a execução de A) e 1
segundo.
EJB: cassandraCluster.getCassandraSession().execute().
package br.com.devmedia.webshelf.service;
import java.util.Set;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.mapping.Mapper;
import br.com.devmedia.webshelf.data.CassandraCluster;
import br.com.devmedia.webshelf.exception.BusinessRuleException;
import br.com.devmedia.webshelf.model.User;
@Stateless
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class UserBean {
@Inject
private CassandraCluster cassandra;
@Inject
private Validator validator;
BoundStatement insertUser =
cassandra.boundInsertUser();
insertUser.bind(user.getLogin(), user.getName(),
user.getPassword());
if (!result.wasApplied()) {
throw new BusinessRuleException("Login já
existente.");
}
}
if (!constraintViolations.isEmpty()) {
throw new
ConstraintViolationException(constraintViolations);
}
}
}
Contudo, como dito na primeira parte deste tutorial, essa feature deve
ser usada com moderação, pois impacta fortemente na performance.
Nesse exemplo, optou-se por fazer uso de LWT porque a regra de
unicidade de usuário é considerada crítica para a aplicação.
Figura 1. Exemplo de race condition – Fonte: FinishJUG.
Implementação do Login
Como em quase todas as aplicações web, também iremos desenvolver
um mecanismo de login. O funcionamento deste será simples: o
usuário terá que informar o seu login e sua senha e, caso tudo esteja
correto, deverá ser redirecionado para a página inicial da aplicação,
caso contrário, uma mensagem de erro deverá ser mostrada. Para
quem ainda não tem cadastro, será disponibilizado um botão que
levará o usuário para a tela de Cadastro de Usuário.
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:p="http://primefaces.org/ui"
template="/WEB-INF/templates/default.xhtml">
<ui:param name="mainContentTitle" value="WebShelf" />
<ui:param name="renderedMenuBar" value="false" />
<ui:define name="mainContent">
<h:form prependId="false">
<div class="ui-fluid">
<p:panelGrid columns="3"
layout="grid"
columnClasses="ui-
grid-col-1,ui-grid-col-4,ui-grid-col-7"
styleClass="ui-
panelgrid-blank">
<p:outputLabel
for="userLogin" value="Login" />
<p:inputText
id="userLogin" autocomplete="false"
value="#{loginController.login}" />
<p:message
for="userLogin" />
<p:outputLabel
for="userPassword" value="Senha" />
<p:password
id="userPassword" autocomplete="false"
value="#{loginController.password}" />
<p:message
for="userPassword" />
</p:panelGrid>
<p:panelGrid columns="3"
layout="grid"
columnClasses="ui-
grid-col-1,ui-grid-col-2,ui-grid-col-2"
styleClass="ui-
panelgrid-blank">
<h:outputText />
<p:commandButton
value="Entrar"
action="#{loginController.doLogin()}" ajax="false"
validateClient="true" />
<p:button
value="Cadastrar-se" outcome="insertUser" />
</p:panelGrid>
</div>
</h:form>
</ui:define>
</ui:composition>
Uma coisa a observar é que nessa página temos dois p:panelGrid. Isso
foi necessário porque, para gerar uma melhor visualização da tela, as
configurações dos tamanhos das colunas são diferentes para a área
dos campos texto (Login e Senha) e a área dos botões
(Entrar e Cadastrar-se). Enquanto a primeira área usa os tamanhos 1, 4
e 7 (columnClasses="ui-grid-col-1,ui-grid-col-4,ui-grid-col-7") para
suas três colunas, a segunda área usa os tamanhos 1, 2 e 2
(columnClasses="ui-grid-col-1,ui-grid-col-2,ui-grid-col- 2").
Ainda com relação aos botões, o primeiro (Entrar) será utilizado para
fazer o login e o segundo (Cadastrar-se) servirá para redirecionar o
usuário para a tela de cadastro de usuário, caso o mesmo ainda não
tenha se registrado no site.
package br.com.devmedia.webshelf.controller;
// imports omitidos...
@Named
@RequestScoped
public class LoginController implements Serializable {
private static final long serialVersionUID = 1L;
@Inject
private HttpServletRequest request;
@Inject
private UserBean userBean;
@Inject
private JsfMessage<Messages> messages;
String encryptedPassword =
User.encryptPassword(password);
if(user==null ||
!user.getPassword().equals(encryptedPassword)){
messages.addWarn().invalidCredentials();
return null;
}
if(request.getSession(Boolean.FALSE) != null){
request.getSession(Boolean.FALSE).invalidate();
}
request.getSession().setAttribute("loggedInUser",
user);
return "/private/home.xhtml?faces-redirect=true";
}
Visto que não precisará guardar nenhum estado entre requests, esta
classe é um bean CDI com escopo de requisição (@RequestScoped). E
como ela será acessada nos XHTMLs (login.xhtml e default.xhtml)
através de Expression Language, também foi anotada com @Named.
investir tempo para criar um módulo de login que possa utilizar o Cassandra
package br.com.devmedia.webshelf.util;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpSession;
import br.com.devmedia.webshelf.model.User;
@Inject
private HttpSession session;
@Produces
@LoggedInUser
@SessionScoped
@Named("loggedInUser")
protected User getLoggedInUser() {
User loggedInUser =
(User)session.getAttribute("loggedInUser");
if (loggedInUser == null) {
loggedInUser = new User();
}
return loggedInUser;
}
}
package br.com.devmedia.webshelf.util;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface LoggedInUser {
Por padrão, toda classe é produzida pelo CDI através de seu construtor
sem argumentos, no entanto, é possível instruí-lo a criar o bean de
outras maneiras. No caso de User, criamos o
método ResourceProducer.getLoggedInUser() , que irá produzir
beans que representam um usuário logado, e para denotar isso, este
método foi anotado com @LoggedInUser. Dessa forma, quando uma
classe for injetar um objeto User, o CDI terá agora duas opções para
gerar o objeto: a opção default (construtor sem argumento) e o
método ResourceProducer.getLoggedInUser().
@Inject
@LoggedInUser
private User loggedInUser;
@Inject
private User dummyUser;
package br.com.devmedia.webshelf.security;
import javax.enterprise.event.Observes;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.servlet.http.HttpServletRequest;
import org.apache.deltaspike.jsf.api.listener.phase.AfterPhase;
import org.apache.deltaspike.jsf.api.listener.phase.JsfPhaseId;
import br.com.devmedia.webshelf.model.User;
import br.com.devmedia.webshelf.util.LoggedInUser;
context.getApplication().getNavigationHandler().handleNavigation(context,
null, page + "?faces-
redirect=true");
}
}
Cadastro de Livro
O cadastro de livro permitirá ao usuário do WebShelf cadastrar novos
livros que, por ventura, ele não tenha conseguido encontrar na
pesquisa. Essa funcionalidade seguirá o mesmo molde do cadastro de
usuário, que é composto de uma tela JSF, um controller e um bean de
negócio.
Nota: Atualmente a API de object-mapping é bastante limitada, e por conta disso seu
uso será restrito a cenários mais simples, normalmente CRUDs sem qualquer feature
package br.com.devmedia.webshelf.model;
import java.nio.ByteBuffer;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotBlank;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((isbn == null) ? 0 :
isbn.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Book other = (Book) obj;
if (isbn == null) {
if (other.isbn != null)
return false;
} else if (!isbn.equals(other.isbn))
return false;
return true;
}
}
<ui:define name="mainContent">
<h:form prependId="false" enctype="multipart/form-
data">
<div class="ui-fluid">
<p:panelGrid columns="3"
layout="grid"
columnClasses="ui-
grid-col-1,ui-grid-col-4,ui-grid-col-7"
styleClass="ui-
panelgrid-blank">
<p:outputLabel
for="bookTitle" value="Título:" />
<p:inputText
id="bookTitle" value="#{bookController.book.title}"
/>
<p:message
for="bookTitle" />
<p:outputLabel
for="bookAuthor" value="Autor:" />
<p:inputText
id="bookAuthor" value="#{bookController.book.author}"
/>
<p:message
for="bookAuthor" />
<p:outputLabel
for="bookPublisher" value="Editora:" />
<p:inputText
id="bookPublisher"
value="#{bookController.book.publisher}" />
<p:message
for="bookPublisher" />
<p:outputLabel
for="bookCountry" value="País:" />
<p:inputText
id="bookCountry"
value="#{bookController.book.country}" />
<p:message
for="bookCountry" />
<p:outputLabel
for="bookIsbn" value="ISBN:" />
<p:inputText
id="bookIsbn" value="#{bookController.book.isbn}" />
<p:message
for="bookIsbn" />
</p:panelGrid>
<br />
<p:panelGrid columns="1"
layout="grid"
columnClasses="ui-
grid-col-1"
styleClass="ui-
panelgrid-blank">
<p:fileUpload
id="bookImage" label="Imagem" auto="true"
allowTypes="/(\.|\/)(jpg|jpeg|png)$/" sizeLimit="20480"
value="#{bookController.image}"
fileUploadListener="#{bookController.uploadImage}"
process="@this"
update="@this bookImageName">
</p:fileUpload>
<h:outputText
id="bookImageName"
bookController.image.fileName}" />
<h:outputText />
<p:commandButton
value="Salvar" action="#{bookController.insert}"
essa definição:
Vale ressaltar que mesmo sendo permitido o uso do in, essa clausula é
vista como uma má prática por vários desenvolvedores, pois pode
obrigar o Cassandra a obter dados de vários nós diferentes numa
única operação. Por exemplo, digamos que no in você informe três
valores, e suponha que o particionador do Cassandra distribuiu os
dados dessas três chaves em máquinas diferentes. Nesse cenário, uma
única consulta fará com que o Cassandra colete dados de três nós
distintos para então retornar a resposta para o cliente. Esse tipo de
operação envolvendo várias máquinas acarreta uma perda de
performance considerável e por isso deve ser evitada.
package br.com.devmedia.webshelf.service;
//imports omitidos...
@Stateless
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class BookBean {
@Inject
private CassandraCluster cassandra;
@Inject
private Validator validator;
batch.add(cassandra.boundInsertBookByISBN().bind(book.getIsbn(),
book.getTitle(), book.getAuthor(),
batch.add(cassandra.boundInsertBookByTitle().bind(book.getIsbn(),
book.getTitle(), book.getAuthor(),
batch.add(cassandra.boundInsertBookByAuthor().bind(book.getIsbn(),
book.getTitle(), book.getAuthor(),
cassandra.execute(batch);
}
if (!constraintViolations.isEmpty()) {
throw new
ConstraintViolationException(constraintViolations);
}
}
}
(?,?,?,?,?,?);");
}
Para encerrar, vale ressaltar que se você não leu a primeira parte terá
bastante dificuldade em compreender aspectos mais avançados e
abstratos da arquitetura do Cassandra. Por isso, aconselhamos que dê
um passo atrás e leia o artigo introdutório, onde foi apresentado um
embasamento teórico essencial para evoluir no aprendizado dessa
tecnologia.