Search code examples
javamongodb-javaalphanumeric

How to save alphanumeric sequence in mongodb and start generating from the last saved sequence in MongoDB


I have a method that generates an alphanumeric sequence based on a particular pattern. I want to save the generated sequence and when next I want to generate a new one, it should start from the last saved one. I am having an issue with this happening because it is alphanumeric. Also, I want to put the generated IDs in an excel file.

These two methods below check if it already exists and also try to fetch the last item saved.

 public Optional<Terminal> findByGeneratedTerminalIDs(String id){
        return Optional.ofNullable(this.collection.findOne(Filters.eq("generatedTerminalID", id)));
    }

    public Optional<FindIterable<Terminal>> findLastGeneratedTerminalID(String generatedTerminalID) {
        return Optional.ofNullable(this.collection.find(Filters.eq("generatedTerminalID", generatedTerminalID)).
                sort(Sorts.descending("generatedTerminalID")).limit(1));

    }

Here is the updated repository

private JacksonMongoCollection<Sequence> collection;

    public SequenceRepository(JacksonMongoCollection<Sequence> collection) {
        this.collection = collection;
    }


public Sequence save(Sequence sequence) {
    collection.save(sequence);
    return sequence;
}

public Sequence findAllSequences(Sequence sequence) {
    return collection.find(Filters.eq("generatedTerminalID", sequence.getGeneratedTerminalID())).first();
}

public BaseResponse generateTerminalIDs(TerminalIDDTO terminalDto) {

Sequence sequence = new Sequence();
sequence.setNext(0);
sequenceRepository.save(sequence);

int totalLength = 8;
int numberOfIds = terminalDto.getNumberOfTerminals();
int countRemainingSymbols = totalLength - START.length();
//there should be only 1 row at all times
sequence = this.sequenceRepository.findAllSequences(sequence);
int start = sequence.getNext();//start generation of sequences from the value of next
int next = start + numberOfIds;//this will be next value of sequence.next
for (int i = start; i < next; i++) {
    StringBuilder end = new StringBuilder();
    int current = i;
    int remainder = current % ALPHANUMERIC.length();//the index of next character
    do {
        end.append(ALPHANUMERIC.charAt(remainder));
        current /= ALPHANUMERIC.length();//update to check if we need to add more characters
        remainder = current % ALPHANUMERIC.length();//update index, only used if more chars are needed
    } while (current > 0);
    int padCount = countRemainingSymbols - end.length();
    StringBuilder result = new StringBuilder(START).append("-");//- is for easier debugging, remove it for your case
    for (int j = 0; j < padCount; j++) {
        result.append("0");
    }
    result.append(end.reverse());
    log.info("These are the values {}", result);
}
//update next value and save in db
sequence.setNext(next);
sequenceRepository.save(sequence);

return BaseResponse.builder().status(true).message("TerminalIDs have been generated").build();

}

I want the saving and checking if it exists to be done in this method I posted above. What is the best way to go about it?


Solution

  • You could do it adding a new table to db. Since it has to be unique for entire db, this table will hold a single row, which keeps track of next integer to use for id generation.

    I'll assume you use spring data, then your entity could be something like this:

    public class Sequence {
    
      private String id;
      private int next;
    
      //getters and setters
    }
    

    The repository:

    @Repository
    public interface SequenceRepository extends MongoRepository<Sequence, String> {
    }
    

    Assuming you are using mongo from question tag.

    Initial value for next should be zero.

    Sequence sequence = new Sequence();
    sequence.setNext(0);
    this.repository.saveAndFlush(sequence);
    

    Initialise and save in db at appropriate time, maybe app startup. Just do this initialization only once.

    Then generation method might be like this:

    @Autowired
    private SequenceRepository repository;
    
    public BaseResponse generateTerminalIDs(TerminalIDDTO terminalDto) {
      Terminal terminal = new Terminal();
      int totalLength = 8;
      int numberOfIds = terminalDto.getNumberOfTerminals();
      int countRemainingSymbols = totalLength - START.length();
      //there should be only 1 row at all times
      Sequence sequence = this.repository.findAll().get(0);
      int start = sequence.getNext();//start generation of sequences from the value of next
      int next = start + numberOfIds;//this will be next value of sequence.next
      for (int i = start; i < next; i++) {
        StringBuilder end = new StringBuilder();
        int current = i;
        int remainder = current % ALPHANUMERIC.length();//the index of next character
        do {
          end.append(ALPHANUMERIC.charAt(remainder));
          current /= ALPHANUMERIC.length();//update to check if we need to add more characters
          remainder = current % ALPHANUMERIC.length();//update index, only used if more chars are needed
        } while (current > 0);
        int padCount = countRemainingSymbols - end.length();
        StringBuilder result = new StringBuilder(START).append("-");//- is for easier debugging, remove it for your case
        for (int j = 0; j < padCount; j++) {
          result.append("0");
        }
        //this should be outside pad loop
        result.append(end.reverse());
      }
      //update next value and save in db
      sequence.setNext(next);
      this.repository.saveAndFlush(sequence);
      return BaseResponse.builder().status(true).message("TerminalIDs have been generated").build();
    }
    

    There are some assumptions here, but this is the overall algorithm you should follow.

    Important: This solution does not take into account possible simultaneous access to the table, which may lead to duplicating ids. If you have such use cases, you might want to consider:

    1. Locking the table during generateTerminalIDs execution, thus forcing other threads who want access for next to wait until all ids are generated.
    2. Get next, and update it right after, before id generation. Locking the table during get and update may still be a good idea, but other threads that want access will wait less.

    Edit: SaveAndFlush is equivalent for create or update. In spring it's implemented in this way - if entity has id property(sequence.id != null in this case), do an update, otherwise do create(insert). For your case, create the row when initializing new table, update it when ids are created.

    About findAll() - it returns a list with all rows/entites from a table. Here repository.findAll() will return List<Sequence>. repository.findAll().get(0) will return Sequence, the first and only row in the table.