Una de las mayores barreras para cualquiera que quiera empezar a utilizar nuevas tecnologías suele ser la curva de aprendizaje. A menudo, al iniciar un nuevo proyecto, acabamos optando por utilizar lo que ya conocemos para evitar cualquier fricción nada más empezar.
He pasado la mayor parte de mi carrera trabajando como desarrollador Java, y en los últimos años me enamoré del APP + Spring-Boot + Lombok + Datos de primavera combinación, pero lo único que me seguía molestando era el mapeado de las relaciones.
JPA es conocido por cargar datos innecesarios de la base de datos, y con el tiempo te ves obligado a revisar algunas de tus entidades para cambiar algunas relaciones de EAGER a LAZY. Puede mejorar significativamente su rendimiento, ya que evitará un montón de JOINS innecesarios, pero no es gratis. Se le pedirá que haga un montón de refactorización para cargar esos nuevos objetos perezosos cada vez que sean necesarios.
Este patrón común siempre me molestó y me alegré mucho cuando descubrí que Spring Data y Couchbase pueden conectarse (documento completo). Es simplemente la mejor parte de dos mundos, puedo programar como lo haría en una base de datos relacional pero aprovechando toda la velocidad de Couchbase y la potencia de N1QL. Veamos cómo configurar un proyecto simple.
Configuración de Spring Data, Spring Boot y Couchbase
Requisitos previos:
- Asumiré que ya tienes instalado Couchbase, si no es así, por favor descárguelo aquí
- Yo también uso Lombok, así que puede que necesites instalar el plugin de Lombok en tu IDE: Eclipse y IntelliJ IDEA
Primero, puedes clonar mi proyecto:
1 |
git clonar https://github.com/deniswsrosa/couchbase-spring-data-sample.git |
o simplemente vaya a Spring-Boot Initialzr y añade Couchbase y Lombok como dependencias:
Nota: Lombok no es una dependencia obligatoria, pero ayuda a reducir significativamente su base de código.
Ahora, vamos a definir la configuración de tu cubo en el archivo aplicación.propiedades file:
1 2 3 4 |
primavera.couchbase.arranque-alberga=localhost primavera.couchbase.cubo.nombre=prueba primavera.couchbase.cubo.contraseña=couchbase primavera.datos.couchbase.auto-índice=verdadero |
Y ya está. Ya puede poner en marcha su proyecto:
1 |
mvn primavera-arranque:ejecute |
Asignación de una entidad
Hasta ahora, nuestro proyecto no hace nada. Vamos a crear y mapear nuestra primera entidad:
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 @Datos @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode público clase Edificio { @NotNull @Id privado Cadena id; @NotNull @Campo privado Cadena nombre; @NotNull @Campo privado Cadena companyId; @Campo privado Lista<Area> zonas = nuevo ArrayList<>(); @Campo privado Lista<String> números de teléfono = nuevo ArrayList<>(); } |
-
- @Documento: Anotación de Couchbase que define una entidad, similar a @Entidad en JPA. Couchbase añadirá automáticamente una propiedad llamada Clase en el documento para utilizarlo como tipo de documento.
- @Data: Anotación de Lombok, getters y setters autogenerados
- @AllArgsConstructor: Anotación de Lombok, auto-generar un constructor utilizando todos los campos de la clase, este constructor se utiliza en nuestras pruebas.
- @NoArgsConstructor: anotación de Lombok, autogenerar un constructor sin argumentos (requerido por Spring Data)
- @EqualsAndHashCode: Los métodos de anotación, autogeneración de iguales y hashcode de Lombok, también utilizados en nuestras pruebas.
- @NotNull: Sí, puedes usar javax.validation con Couchbase.
- @Id: La clave del documento
- @Campo: Las anotaciones de Couchbase, similares a @Columna
Mapear entidades en Couchbase es realmente simple y directo, la mayor diferencia aquí es el @Campo entidad que se utiliza de 3 formas diferentes:
- Propiedad simple: En casos como id, nombre y companyId, el @Campo actúa más o menos como el @Columna en JPA. Resultará en una simple propiedad en el documento:
1 2 3 4 5 |
{ "id": "edificio::1", "nombre": "Edificio Couchbase", "companyId": "empresa::1" } |
- Matrices: En el caso de phoneNumbers resultará en un array dentro del documento:
1 2 3 |
{ "NúmerosDeTeléfono": ["phoneNumber1", "númeroTeléfono2"] } |
- Entidades: Por último, en el caso de las áreas, @Campo actúa como un @ManyToOne la principal diferencia es que no es necesario asignar nada en la entidad Área:
1 2 3 4 5 6 7 8 9 10 11 12 |
@EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Datos público clase Zona { privado Cadena id; privado Cadena nombre; privado Lista<Zona> zonas = nuevo ArrayList<>(); } |
Repositorios
Sus repositorios tendrán un aspecto muy similar a los repositorios estándar de Spring Data, pero con algunas anotaciones adicionales:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@N1qlPrimaryIndexed @VerIndexado(designDoc = "edificio") público interfaz EdificioRepositorio extiende CouchbasePagingAndSortingRepository<Edificio, Cadena> { Lista<Edificio> findByCompanyId(Cadena companyId); Página<Edificio> findByCompanyIdAndNameLikeOrderByName(Cadena companyId, Cadena nombre, Pageable paginable); @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and companyId = $1 and $2 within #{#n1ql.bucket}") Edificio findByCompanyAndAreaId(Cadena companyId, Cadena areaId); @Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} AND ANY phone IN phoneNumbers SATISFIES phone = $1 END") Lista<Edificio> findByPhoneNumber(Cadena número de teléfono); @Consulta("SELECT COUNT(*) AS count FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} and companyId = $1") Largo countBuildings(Cadena companyId); } |
- @N1qlPrimaryIndexed: Este anotación asegura que el bucket asociado al repositorio actual tendrá un índice primario N1QL
- @ViewIndexed: Este anotación le permite definir el nombre del documento de diseño y el nombre de la vista, así como una función de mapa y reducción personalizada.
En el repositorio anterior, estamos ampliando CouchbasePagingAndSortingRepositoryque le permite paginar sus consultas simplemente añadiendo un icono Pageable al final de la definición del método
Al tratarse esencialmente de un repositorio, puede aprovechar todas las Palabras clave de Spring Data como BuscarEn, Entre, EsMayorQue, Como, Existeetc. Por lo tanto, puede empezar a utilizar Couchbase casi sin conocimientos previos y seguir siendo muy productivo.
Como ya se habrá dado cuenta, puede crear N1QL pero con algunos azúcares sintácticos:
- #(#n1ql.bucket): Utilice esta sintaxis para evitar introducir el nombre del cubo en la consulta
- #{#n1ql.selectEntity}: sintaxis-azúcar a SELECT * FROM #(#n1ql.bucket):
- #{#n1ql.filter}: sintaxis-azúcar para filtrar el documento por tipo, técnicamente significa class = 'miPaquete.MiNombreDeClase' (Clase es el atributo que se añade automáticamente en el documento para definir su tipo cuando se trabaja con Couchbase en Spring Data )
- #{#n1ql.fields} se sustituirá por la lista de campos (por ejemplo, para una cláusula SELECT) necesarios para reconstruir la entidad.
- #{#n1ql.delete} se sustituirá por la sentencia delete from.
- #{#n1ql.returning} se sustituirá por la cláusula de retorno necesaria para reconstruir la entidad.
Para demostrar algunas de las geniales capacidades de N1QL, vamos a profundizar un poco más en dos métodos de nuestro repositorio: findByPhoneNumber y findByCompanyAndAreaId:
findByPhoneNumber
1 2 |
@Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} AND ANY phone IN phoneNumbers SATISFIES phone = $1 END") Lista<Edificio> findByPhoneNumber(Cadena número de teléfono); |
En el caso anterior, simplemente buscamos edificios por números de teléfono. En un mundo relacional, normalmente necesitarías 2 tablas para conseguir casi lo mismo. Con Couchbase podemos almacenar todo en un único documento, lo que hace que cargar un "edificio" sea mucho más rápido que lo que conseguirías usando cualquier RDBMS.
Además, puede acelerar aún más el rendimiento de sus consultas si añadir un índice en el atributo phoneNumbers.
findByCompanyAndAreaId
1 2 |
@Consulta("#{#n1ql.selectEntity} where #{#n1ql.filter} and companyId = $1 and $2 within #{#n1ql.bucket}") Edificio findByCompanyAndAreaId(Cadena companyId, Cadena areaId); |
En la consulta anterior, básicamente estamos intentando encontrar el nodo raíz (Edificio) que da un nodo hijo aleatorio (Área). Nuestros datos están estructurados en forma de árbol, ya que una zona también puede tener una lista de otras zonas:
1 2 3 4 5 6 7 8 9 10 11 12 |
@EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Datos público clase Zona { privado Cadena id; privado Cadena nombre; privado Lista<Zona> zonas = nuevo ArrayList<>(); } |
Este tipo de consulta es una de las operaciones más caras y complejas cuando se trabaja con una base de datos relacional, en la mayoría de los casos o bien se encuentra el nodo raíz a mano o mediante una consulta con UNIONs y CONECTADO POR.
Aquí puede resolver este problema utilizando una palabra clave mágica llamada EN.
Servicios
Por defecto, inyectarás y utilizarás tus repositorios en tus Servicios como lo harías normalmente, pero adicionalmente puedes acceder a las capacidades específicas de los repositorios de Couchbase utilizando el método getCouchbaseOperations()
Todo en acción
El uso de los servicios es exactamente lo que cabría esperar:
1 |
servicioedificio.findById("edificio::1") |
1 2 3 4 |
Edificio bulding = nuevo Edificio("bulding::1", "Construcción de Couchbase", "empresa::1", nuevo ArrayList<>(), nuevo ArrayList<>()); servicioedificio.guardar(edificio); |
1 |
servicioedificio.findByCompanyIdAndNameLike("empresa::1", "Cou%", 0); |
Echa un vistazo a la clase de prueba de integración BuildingServiceIntegrationTest para verlo todo en acción.
Si tienes alguna pregunta, envíame un tweet a @deniswsrosa o haga una pregunta en nuestro foro
¡Este es un gran blog! Soy bastante nuevo en la primavera y la primavera de arranque . Y estamos configurando CouchBase. Tengo el CouchBase se ejecuta localmente en mi equipo, sin embargo, no encontró ninguna disposición para dar la contraseña para el cubo. Y por lo tanto cuando estoy ejecutando mi aplicación me está dando InvalidPasswordException: Passwords for bucket do not match.
¿Podría indicarme cómo puedo resolver este problema?
Hola Kn,
La forma más sencilla es crear un usuario con el mismo nombre que tu cubo, y luego puedes seguir este tutorial
https://www.couchbase.com/couchbase-spring-boot-spring-data/
Si quieres que varios usuarios accedan al mismo cubo, tendrás que implementar la clase AbstractCouchbaseConfiguration
https://stackoverflow.com/questions/53177777/couchbase-6-0-springboot-invalidpasswordexception
Soy bastante nuevo en Couchbase y spring data, Spring boot implementation. Estoy usando la API de subdocumento de documento para actualizar, insertar y eliminar el subdocumento sin leer todo el documento. Puedo codificar la ruta al subdocumento y hacer operaciones de mutación directamente en el subdocumento. Una de las ventajas de la API es que puedo actuar sobre el subdocumento sin bloquear todo el documento (en mi caso de uso).
¿Cómo puedo lograr lo mismo utilizando spring-data y Spring boot . Veo la mayor diferencia donde spring-data trabaja con N1QL query y POJO donde document subdocument API trabaja con Json.
Referencia al documento Subdocument API: https://docs.couchbase.com/java-sdk/2.7/subdocument-operations.html
Hola Muraic,
Los subdocumentos suelen ser más rápidos, ya que se obtiene el documento por su clave. Sin embargo, los datos de Spring son mucho más productivos.
Puedes usar datos de spring y aún así ser capaz de hacer operaciones con subdocumentos, si llamas a yourRepository.getCouchbaseOperations().getCouchbaseBucket() obtendrás acceso al objeto Bucket, que potencialmente es el que estás usando ahora mismo.
¿Es posible conectarse mediante SSL y utilizar el cifrado de campos con Spring Data Couchbase?
Hola Denis, soy muy - muy nuevo en las tecnologías de sistemas abiertos y estoy tratando de entender cómo se ejecuta su proyecto. He instalado couchbase y creado un documento y capaz de ver el servidor en ejecución en la consola.
Sin embargo, quería ver cómo Java se integrará con couchbase, por lo que importó su proyecto y hacer mvn clean install, pero conseguir errores de autenticación como :
2019-05-21 20:14:31.776 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Fallo de autenticación.
2019-05-21 20:14:31.777 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : Error durante la reconexión:
-05-21 20:14:31.778 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: No se pudo conectar al punto final, reintentando con retraso 4096 MILLISEGUNDOS:
2019-05-21 20:14:35.922 WARN 9672 - [ cb-io-19-1] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Fallo de autenticación.
2019-05-21 20:14:36.020 WARN 9672 - [ cb-io-19-2] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Fallo de autenticación.
2019-05-21 20:14:36.021 WARN 9672 - [ cb-io-19-2] c.c.client.core.endpoint.Endpoint : Error durante la reconexión:
2019-05-21 20:14:36.029 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Fallo de autenticación.
2019-05-21 20:14:36.030 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : Error durante la reconexión:
2019-05-21 20:14:36.362 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Fallo de autenticación.
2019-05-21 20:14:36.363 WARN 9672 - [ cb-io-19-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: No se pudo conectar con el endpoint, reintentando con retardo 32 MILLISEGUNDOS:
¿Podría usted por favor aconsejar, si me estoy perdiendo algún paso. Tengo que hacer algún ajuste con respecto a traer couchbase servidor localhost o me falta couchbase dependencias / No tengo ni idea, por favor consejo.
Hola Denis, soy muy - muy nuevo en las tecnologías de sistemas abiertos y estoy tratando de entender cómo se ejecuta su proyecto. He instalado couchbase y creado un documento y capaz de ver el servidor en ejecución en la consola.
Sin embargo, quería ver cómo Java se integrará con couchbase, por lo que importó su proyecto y hacer mvn clean install, pero conseguir errores de autenticación como :
2019-05-21 20:14:31.776 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : [null][KeyValueEndpoint]: Fallo de autenticación.
2019-05-21 20:14:31.777 WARN 9672 - [ cb-io-17-3] c.c.client.core.endpoint.Endpoint : Error durante la reconexión:
¿Podría usted por favor aconsejar, si me estoy perdiendo algún paso. Tengo que hacer algún ajuste con respecto a traer couchbase servidor localhost o me falta couchbase dependencias / No tengo ni idea, por favor consejo.
nunca, mente, encontré la solución :) pero tendré más dudas en los próximos días...
Publica tus preguntas en StackOverflow, puedo responderlas allí.
Hola Rosa,
Soy nuevo en Couchbase con datos de Spring, pero basado en la I + D soy capaz de hacer la operación CRUD fácilmente. Todavía me estoy enfrentando a un problema de "clave":{"vacío": "false"} mientras almaceno el valor de la lista.
También ha descrito más arriba que :-
privado List phoneNumbers = new ArrayList();
y,
Matrices: En el caso de phoneNumbers resultará en un array dentro del documento:
así : -
{
"phoneNumbers": ["phoneNumber1", "phoneNumber2"]
}
Pero siempre se guarda como :- "key":{"empty": "false"}.
> Pero siempre se guarda como :- "key":{"empty": "false"}.
Este problema se ha solucionado en spring-data-couchbase 4.0.2
Saludos