control simultaneous calls to a REST service without duplicating the ID in JAVA

3

I'm doing a REST service through Spring and swagger with CMIS protocol. my service works well until the moment I make simultaneous calls through Jmeter to be able to stress the system. Context: the service obtains the ID from an ID creator to generate the node. The id can not be repeated in the system and it takes the last created node and adds 1. I tried with Thread.sleep and with TimeUnit. The jmeter throws that the first and the third are created with the same id and the other 8 answers were that the id exists, therefore it does not create another id as it should be or create nodes nodes.

current code extract: Controller

@ApiOperation(value = "Crea un Tipo de documento")
@RequestMapping(value = "/create2", method = RequestMethod.POST)
ResponseMessage createTypeDocument2(@RequestParam String description) throws InterruptedException {     
    int id = typeDocumentService.idCreator2();
    return typeDocumentService.createTypeDocument(id,description);
}

Service

public ResponseMessage createTypeDocument(int id, String description) throws InterruptedException {
    Thread.sleep(1000);     
    Session session = obtieneSesion();      
    ResponseMessage rm = new ResponseMessage();     
    TimeUnit.SECONDS.sleep(1);      
    if(searchDocuments(id)==null ) {        
        SimpleDateFormat sdf = new SimpleDateFormat("SSS");
        Date date = new Date();
        String nombre = sdf.format(date.getTime());                 
        DocumentTypeDTO docFilter = new DocumentTypeDTO();
        Folder root = (Folder) session.getObjectByPath("/DataList-BCH");            
        HashMap<String, Object> metadata = new HashMap<String, Object>();
        metadata.put(PropertyIds.OBJECT_TYPE_ID, "********");
        metadata.put(PropertyIds.NAME, nombre); 
        Document newDoc = root.createDocument(metadata, null, null);            
        docFilter.setId( Integer.parseInt(newDoc.getProperty("bc:id").getValueAsString()));
        docFilter.setDescription(newDoc.getProperty("bc:description").getValueAsString());
        docFilter.setUuid(newDoc.getId().replaceAll(";1.0", "").replace("workspace://SpacesStore/", ""));   

        rm.setMensaje("Exitoso");
        rm.setCodigo(200);
        rm.setObjeto(docFilter);
        return rm;
    }else {
        rm.setCodigo(-1);
        rm.setMensaje("La ID ya existe");
        return rm;
    }       
}

The searchDocument method if I do not find the object with that id returns null, therefore creates the new node and the new DTO.

already tried several options without result, that's why I need your help to solve it. in advance thank you very much

EDIT: idCreator2 (); obviously I have another creator of id's but I think this works better

private int idCreator2() {          

    Session session = obtieneSesion();
    String queryString = "select * from bc:baseListData";           
    List<Integer> idList = new ArrayList<Integer>();    
    int id=1;
    ItemIterable<QueryResult> results = session.query(queryString, false);
    for (QueryResult qResult : results) {
        String objectId = "";
        PropertyData<?> propData = qResult.getPropertyById("cmis:objectId");
        if (propData != null) {
            objectId = (String) propData.getFirstValue();
        } else {
            objectId = qResult.getPropertyValueByQueryName("d.cmis:objectId");
        }
        CmisObject obj = session.getObject(session.createObjectId(objectId));
        idList.add(Integer.parseInt(obj.getPropertyValue("bc:id").toString()));         
    }       
    Collections.sort(idList);
    if (!idList.isEmpty()){
        id = idList.get(idList.size()-1)+1;
    }

    return id;
}   
    
asked by Héctor Mancilla 04.04.2018 в 15:39
source

2 answers

2

I do not think that adding a transaction solutions your problem.

You could declare a method synchronized that is responsible for creating the id and the document. Effectively blocking all the threads that try to create the id's in simultaneous, assuming that TypeDocumentService is Singleton (by default, in spring):

public synchronized ResponseMessage crearDocumento(String description) {
    int id = this.idCreator2();
    return this.createTypeDocument(id,description);
}

But this solution is fragile, it will only work so that your particular application does not create duplicate ids.

Concurrently, you could modify your repository directly through another channel and duplicate those ids following a logic similar to the one you are using for the creation.

I would recommend you follow a different id creation strategy, for example with UUID :

String id = UUID.randomUUID().toString();

This identifier for all practical purposes is unique. Quoted from the wikipedia entry:

  

Thus, for there to be a one in a billion chance of duplication, 103   trillion version 4 UUIDs must be generated.

    
answered by 04.04.2018 / 18:04
source
0

There are reasons why I think you should change the way you save a new item:

  • The @Controller does not need to know how your logic works, only what it does. A call to the service should be enough.
  • A @Service method can contain a single transaction, thus ensuring atomicity. If you have two calls, you have two transactions, so it is possible for two or more wires to call the idCreator2 method consecutively, before the other keeps anything. In that case you will find repeated IDs.
  • My advice: One call to the service and he will take care of everything

    @ApiOperation(value = "Crea un Tipo de documento")
    @RequestMapping(value = "/create2", method = RequestMethod.POST)
    ResponseMessage createTypeDocument2(@RequestParam String description) throws InterruptedException {     
    
        return typeDocumentService.createTypeDocument(description);
    }
    

    and in the service:

    public ResponseMessage createTypeDocument(String description) throws InterruptedException {
    
        int id = this.idCreator2(); //método privado!!!
    
        ... //resto del código
    
    }
    

    Note: It seems that the Spring% annotation @Transaction does not work with Alfresco (I guess it will not detect the resources on which to make the lock ), but this use your own manager from transactions

        
    answered by 04.04.2018 в 15:54