Spring-data + Mongo: Secuencia autoincremental en MongoDB mediante un servicio de Spring
Introducción
En este post mostraré cómo asignar un valor de una secuencia incrementada de forma automática a un campo empleando una colección de mongo. Para ello emplearemos la misma técnica que se muestra en el tutorial de mongodb.com pero con métodos puramente Java.
Es importante reseñar que este valor no debería de emplearse como identificador único del documento, Mongo ya dispone de mejores métodos para asignar identificadores a documentos, y por lo general sería una mala práctica. Por otro lado, en sistemas distribuidos o en bases de datos con una enorme cantidad de documentos, tampoco debería de emplearse esta metodología.
Este sistema es útil cuando hay que asignar identificadores públicos o de fácil memorización para ser empleados por usuarios del sistema (números de facturas, número de proveedor, número de envío, etc.)
Colección de secuencias
Del mismo modo que se muestra en el tutorial mencionado, vamos a disponer de un @Document para cada una de las secuencias que utilicemos. En este documento se alojará el último valor de la secuencia utilizado para cada una de ellas.
Es decir, para la versión propuesta en notación JSON:
1{
2 _id: "userid",
3 seq: 0
4}
Vamos a crear la siguiente clase Java:
1@Document(collection = "sequences")
2public class Sequence {
3 @Id
4 private String id;
5 private Long value;
6//....//
7}
Por otro lado, crearemos un servicio de Spring que nos permitirá obtener el siguiente valor de cada una de las secuencias de forma segura.
Para ello, en el tutorial original se emplea la siguiente función Javascript:
1function getNextSequence(name) {
2 var ret = db.counters.findAndModify(
3 {
4 query: { _id: name },
5 update: { $inc: { seq: 1 } },
6 new: true
7 }
8 );
9
10 return ret.seq;
11}
Esta función la añadimos a un @Service de Spring que podemos emplear cuando guardemos el documento que hará uso de la secuencia:
1@Service
2public class SequenceService {
3 @Autowired
4 private MongoOperations mongo;
5
6//...//
7
8 public Long getNextValue(String sequenceId) {
9 //https://docs.mongodb.com/v3.0/tutorial/create-an-auto-incrementing-field/
10 final Sequence sequence = mongo.findAndModify(
11 query(where("_id").is(sequenceId)),
12 new Update().inc("value",1),
13 options().returnNew(true).upsert(true),
14 Sequence.class);
15 return sequence.getValue();
16 }
17}
La función anterior podría emplearse posteriormente en otro servicio o repositorio a la hora de guardar un documento (e.g. una factura):
1@Service
2public InvoiceService {
3
4 @Autowired
5 private SequenceService sequenceService;
6
7 @Autowired
8 private InvoiceRepository invoiceRepository;
9
10//...//
11
12 public Invoice insert(Invoice newInvoice) {
13//...//
14 newInvoice.setPublicId(sequenceService.getNextValue("INV");
15//...//
16 return invoiceRepository.insert(newInvoice)
17 }
18
19//...//
20
21}
Función findAndModify
La función principal que se está empleando es findAndModify()
que modifica y devuelve un único documento . Es importante señalar que la función debería de aplicarse filtrando sobre un campo que permita seleccionar la secuencia de forma unívoca. En este caso, localizaremos la secuencia por “_id”.
El trozo de código new Update().inc(“value”,1)
incrementará unitariamente el campo “value”.
La opción returnNew(true)
hará que la función devuelva el nuevo documento en lugar del original.
La opción upsert(true)
creará un nuevo documento si la secuencia indicada no existe previamente.
Descargar código fuente
Podéis encontrar un proyecto de muestra con el código mostrado en este tutorial que cuenta con un @RestController
para recuperar valores de secuencias y un test unitario para comprobar que se generan y almacenan correctamente en el siguiente enlace: