Uma das maiores barreiras para quem quer começar a usar novas tecnologias geralmente é a curva de aprendizado. Muitas vezes, ao iniciar um novo projeto, acabamos optando por usar o que já sabemos para evitar qualquer atrito logo no início.
Passei a maior parte de minha carreira trabalhando como desenvolvedor Java e, nos últimos anos, me apaixonei pela tecnologia JPA + Pé de mola + Lombok + Dados do Spring mas a única coisa que ainda me incomodava era o mapeamento de relacionamentos.
A JPA é conhecida por carregar dados desnecessários do banco de dados e, com o tempo, você é forçado a revisitar algumas de suas entidades para alterar alguns relacionamentos de EAGER para LAZY. Isso pode melhorar significativamente seu desempenho, pois você evitará muitos JOINS desnecessários, mas não é gratuito. Você terá que refatorar bastante para carregar esses novos objetos lazy sempre que necessário.
Esse padrão comum sempre me incomodou e fiquei muito feliz quando descobri que o Spring Data e o Couchbase podem se conectar (documento completo aqui). É simplesmente a melhor parte de dois mundos: posso programar como faria em um banco de dados relacional, mas ainda aproveitando toda a velocidade do Couchbase e o poder do N1QL. Vamos ver como configurar um projeto simples.
Configuração do Spring Data, do Spring Boot e do Couchbase
Pré-requisitos:
- Presumo que você já tenha o Couchbase instalado. Se não tiver, por favor Faça o download aqui
- Também estou usando o Lombok, portanto, talvez seja necessário instalar o plug-in do Lombok em seu IDE: Eclipes e IntelliJ IDEA
Primeiro, você pode clonar meu projeto:
1 |
git clone https://github.com/deniswsrosa/couchbase-spring-data-sample.git |
ou simplesmente vá para Inicialização do Spring-Boot e adicione o Couchbase e o Lombok como dependências:
Observação: O Lombok não é uma dependência obrigatória, mas ajuda a reduzir significativamente sua base de código.
Agora, vamos definir a configuração do seu bucket no application.properties file:
1 2 3 4 |
mola.couchbase.bootstrap-anfitriões=localhost mola.couchbase.balde.nome=teste mola.couchbase.balde.senha=couchbase mola.dados.couchbase.automático-índice=verdadeiro |
E é isso! Você já pode iniciar seu projeto usando:
1 |
mvn mola-inicialização:executar |
Mapeamento de uma entidade
Até o momento, nosso projeto não faz nada. Vamos criar e mapear nossa primeira entidade:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
@Documento @Data @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode público classe Edifício { @NotNull @Id privado Cordas id; @NotNull @Campo privado Cordas nome; @NotNull @Campo privado Cordas companyId; @Campo privado Lista<Area> áreas = novo ArrayList<>(); @Campo privado Lista<String> phoneNumbers = novo ArrayList<>(); } |
-
- @Document: A anotação do Couchbase que define uma entidade, semelhante a @Entidade em JPA. O Couchbase adicionará automaticamente uma propriedade chamada _classe no documento para usá-lo como o tipo de documento.
- @Data: Anotação do Lombok, gerador automático de getters e setters
- @AllArgsConstructor: A anotação do Lombok gera automaticamente um construtor usando todos os campos da classe, esse construtor é usado em nossos testes.
- @NoArgsConstructor: A anotação do Lombok gera automaticamente um construtor sem argumentos (exigido pelo Spring Data)
- @EqualsAndHashCode: Os métodos de anotação, geração automática de iguais e hashcode do Lombok também foram usados em nossos testes.
- @NotNull: Sim, você pode usar o javax.validation com o Couchbase.
- @Id: A chave do documento
- @Campo: As anotações do Couchbase, semelhantes a @Coluna
O mapeamento de entidades no Couchbase é realmente simples e direto, a maior diferença aqui é o @Campo que é usada de três maneiras diferentes:
- Propriedade simples: Em casos como id, nome e companyId, o @Campo age de forma muito parecida com o @Coluna na JPA. Isso resultará em uma propriedade simples no documento:
1 2 3 4 5 |
{ "id": "building::1", "name" (nome): "Construção do Couchbase", "companyId": "company::1" } |
- Matrizes: No caso do phoneNumbers, isso resultará em uma matriz dentro do documento:
1 2 3 |
{ "phoneNumbers" (números de telefone): ["phoneNumber1", "phoneNumber2"] } |
- Entidades: Finalmente, no caso das áreas, @Campo age como um @ManyToOne a principal diferença é que você não é obrigado a mapear nada na entidade Area:
1 2 3 4 5 6 7 8 9 10 11 12 |
@EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Dados público classe Área { privado Cordas id; privado Cordas nome; privado Lista<Área> áreas = novo ArrayList<>(); } |
Repositórios
Seus repositórios serão muito parecidos com os repositórios padrão do Spring Data, mas com algumas anotações adicionais:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@N1qlPrimaryIndexed @ViewIndexed(designDoc = "edifício") público interface BuildingRepository se estende CouchbasePagingAndSortingRepository<Edifício, Cordas> { Lista<Edifício> findByCompanyId(Cordas companyId); Página<Edifício> findByCompanyIdAndNameLikeOrderByName(Cordas companyId, Cordas nome, Paginável paginável); @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and companyId = $1 and $2 within #{#n1ql.bucket}") Edifício findByCompanyAndAreaId(Cordas companyId, Cordas areaId); @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} AND ANY phone IN phoneNumbers SATISFIES phone = $1 END") Lista<Edifício> findByPhoneNumber(Cordas telephoneNumber); @Consulta("SELECT COUNT(*) AS count FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} and companyId = $1") Longo countBuildings(Cordas companyId); } |
- @N1qlPrimaryIndexed: Este anotação garante que o bucket associado ao repositório atual terá um índice primário N1QL
- @ViewIndexed: Isso anotação permite que você defina o nome do documento de design e o nome da visualização, bem como um mapa personalizado e uma função de redução.
No repositório acima, estamos estendendo CouchbasePagingAndSortingRepositoryque permite que você pagine suas consultas simplesmente adicionando um Paginável param no final da definição do método
Como ele é essencialmente um repositório, você pode aproveitar todos os Palavras-chave do Spring Data como Encontrar, Entre, IsGreaterThan, Como, Existeetc. Portanto, você pode começar a usar o Couchbase sem quase nenhum conhecimento prévio e ainda assim ser muito produtivo.
Como você deve ter notado, é possível criar N1QL mas com alguns detalhes de sintaxe:
- #(#n1ql.bucket): Use esta sintaxe para evitar a codificação do nome do bucket na consulta
- #{#n1ql.selectEntity}: açúcar-sintaxe para SELECT * FROM #(#n1ql.bucket):
- #{#n1ql.filter}: syntax-sugar para filtrar o documento por tipo, o que tecnicamente significa class = 'myPackage.MyClassName' (_classe é o atributo adicionado automaticamente no documento para definir seu tipo quando você está trabalhando com o Couchbase no Spring Data )
- #{#n1ql.fields} será substituído pela lista de campos (por exemplo, para uma cláusula SELECT) necessária para reconstruir a entidade.
- #{#n1ql.delete} será substituído pela instrução delete from.
- #{#n1ql.returning} será substituído pela cláusula de retorno necessária para reconstruir a entidade.
Para demonstrar alguns dos recursos interessantes do N1QL, vamos nos aprofundar um pouco mais em dois métodos do nosso repositório: findByPhoneNumber e findByCompanyAndAreaId:
findByPhoneNumber
1 2 |
@Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} AND ANY phone IN phoneNumbers SATISFIES phone = $1 END") Lista<Edifício> findByPhoneNumber(Cordas telephoneNumber); |
No caso acima, estamos simplesmente procurando edifícios por números de telefone. Em um mundo relacional, normalmente seriam necessárias duas tabelas para realizar praticamente a mesma coisa. Com o Couchbase, podemos armazenar tudo em um único documento, o que torna o carregamento de um "edifício" muito mais rápido do que seria possível usando qualquer RDBMS.
Além disso, você pode acelerar ainda mais o desempenho da consulta adicionar um índice no atributo phoneNumbers.
findByCompanyAndAreaId
1 2 |
@Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and companyId = $1 and $2 within #{#n1ql.bucket}") Edifício findByCompanyAndAreaId(Cordas companyId, Cordas areaId); |
Na consulta acima, estamos basicamente tentando encontrar o nó raiz (Edifício) dando um nó filho aleatório (Área). Nossos dados são estruturados como uma árvore porque uma área também pode ter uma lista de outras áreas:
1 2 3 4 5 6 7 8 9 10 11 12 |
@EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Dados público classe Área { privado Cordas id; privado Cordas nome; privado Lista<Área> áreas = novo ArrayList<>(); } |
Esse tipo de consulta é uma das operações mais caras e complexas quando se trabalha com um banco de dados relacional; na maioria dos casos, você encontra o nó raiz manualmente ou usando uma consulta grande com UNIÃOs e CONECTADOS POR.
Aqui você pode resolver esse problema usando uma palavra-chave mágica chamada DENTRO.
Serviços
Por padrão, você injetará e usará seus repositórios em seus serviços como faria normalmente, mas poderá acessar adicionalmente os recursos específicos de repositórios do Couchbase usando o método getCouchbaseOperations()
Tudo em ação
O uso dos serviços é exatamente o que você espera:
1 |
buildingService.findById("building::1") |
1 2 3 4 |
Edifício construção = novo Edifício("bulding::1", "Construção do Couchbase", "company::1", novo ArrayList<>(), novo ArrayList<>()); buildingService.salvar(construção); |
1 |
buildingService.findByCompanyIdAndNameLike("company::1", "Cou%", 0); |
Confira a classe de teste de integração BuildingServiceIntegrationTest para ver tudo em ação.
Se você tiver alguma dúvida, envie-me um tweet para @deniswsrosa ou faça uma pergunta em nosso fórum
Este é um ótimo blog! Sou muito novo no Spring e no Spring boot. E estamos configurando o CouchBase. Tenho o CouchBase em execução localmente no meu computador, mas não encontrei nenhuma provisão para fornecer a senha do bucket. Por isso, quando estou executando meu aplicativo, ele está dando InvalidPasswordException: Passwords for bucket do not match.
Você poderia me informar como posso resolver isso?
Oi Kn,
A maneira mais fácil é criar um usuário com o mesmo nome do seu bucket e, em seguida, seguir este tutorial
https://www.couchbase.com/couchbase-spring-boot-spring-data/
Se quiser ter vários usuários acessando o mesmo bucket, será necessário implementar a classe AbstractCouchbaseConfiguration
https://stackoverflow.com/questions/53177777/couchbase-6-0-springboot-invalidpasswordexception
Sou novo no Couchbase e no Spring Data, implementação do Spring Boot. Estou usando a API de subdocumento de documento para atualizar, inserir e remover o subdocumento sem ler o documento inteiro. Posso codificar o caminho para o subdocumento e fazer operações de mutação diretamente no subdocumento. Uma das vantagens da API de subdocumento de documento é que posso atuar no subdocumento sem bloquear o documento inteiro (no meu caso de uso).
Como posso fazer o mesmo usando o spring-data e o Spring boot. Vejo que a principal diferença é que o spring-data trabalha com consulta N1QL e POJO, enquanto a API de subdocumento de documento trabalha com Json.
Referência à API do subdocumento do documento: https://docs.couchbase.com/java-sdk/2.7/subdocument-operations.html
Oi Muraic,
Os subdocumentos geralmente são mais rápidos, pois você obtém o documento por sua chave. Entretanto, os dados do Spring são muito mais produtivos.
Se você chamar yourRepository.getCouchbaseOperations().getCouchbaseBucket(), terá acesso ao objeto Bucket, que potencialmente é o que você está usando no momento.
É possível se conectar via SSL e usar a criptografia de campo com o Spring Data Couchbase?
Olá Denis, sou muito novo em tecnologias de sistema aberto e estou tentando entender como seu projeto é executado. Instalei o couchbase, criei um documento e consigo ver o servidor em execução no console.
No entanto, eu queria ver como o Java se integrará ao couchbase, então importei seu projeto e fiz a instalação limpa do mvn, mas estou recebendo erros de autenticação como :
2019-05-21 20:14:31.776 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Falha de autenticação.
2019-05-21 20:14:31.777 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : Erro durante a reconexão:
-05-21 20:14:31.778 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Não foi possível conectar-se ao endpoint, tentando novamente com atraso de 4096 MILISECONDS:
2019-05-21 20:14:35.922 WARN 9672 - [ cb-io-19-1] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Falha de autenticação.
2019-05-21 20:14:36.020 WARN 9672 - [ cb-io-19-2] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Falha de autenticação.
2019-05-21 20:14:36.021 WARN 9672 - [ cb-io-19-2] c.c.client.core.endpoint.Endpoint : Erro durante a reconexão:
2019-05-21 20:14:36.029 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Falha de autenticação.
2019-05-21 20:14:36.030 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : Erro durante a reconexão:
2019-05-21 20:14:36.362 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Falha de autenticação.
2019-05-21 20:14:36.363 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Não foi possível conectar-se ao endpoint, tentando novamente com atraso de 32 MILISECONDS:
Poderia me informar se estou perdendo alguma etapa? Preciso fazer alguma configuração com relação a trazer o servidor localhost do couchbase ou estou perdendo as dependências do couchbase / não tenho nenhuma ideia, por favor, informe.
Olá Denis, sou muito novo em tecnologias de sistema aberto e estou tentando entender como seu projeto é executado. Instalei o couchbase, criei um documento e consigo ver o servidor em execução no console.
No entanto, eu queria ver como o Java se integrará ao couchbase, então importei seu projeto e fiz a instalação limpa do mvn, mas estou recebendo erros de autenticação como :
2019-05-21 20:14:31.776 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Falha de autenticação.
2019-05-21 20:14:31.777 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : Erro durante a reconexão:
Poderia me informar se estou perdendo alguma etapa? Preciso fazer alguma configuração com relação a trazer o servidor localhost do couchbase ou estou perdendo as dependências do couchbase / não tenho nenhuma ideia, por favor, informe.
Não se preocupe, encontrei a solução :) mas terei mais dúvidas nos próximos dias...
Publique suas perguntas no StackOverflow, posso respondê-las lá.
Oi Rosa,
Sou novato no Couchbase com dados do Spring, mas, com base no P&D, consigo fazer operações CRUD facilmente. Ainda assim, estou enfrentando um problema de "key":{"empty": "false"} ao armazenar o valor da lista.
Você também descreveu acima que :-
private List phoneNumbers = new ArrayList();
e,
Matrizes: No caso do phoneNumbers, isso resultará em uma matriz dentro do documento:
assim : -
{
"phoneNumbers": ["phoneNumber1", "phoneNumber2"]
}
Mas ele sempre é salvo como: - "key":{"empty": "false"}.
> Mas ele sempre é salvo como: - "key":{"empty": "false"}.
Esse problema foi corrigido no spring-data-couchbase 4.0.2
Saudações