Search code examples
javasql-servermicronaut

Row locking is not working when using @Transactional for SQL Server database


I am creating a XML file and writing it in one folder with name Doc_type_currentDateTimeYYMMDDHHMMSS format. When multiple threads are executing the same process and creating the file in same second it is getting overridden with the later thread.

@Transactional
public String writeXMLFile(String xmlStr, String fileLocation) {
        try {
            LOGGER.info("Writing xml file for doc type: {}" , docProp.getDocumentType());
            fileRepo.updateFileLastUpdated(docProp.getDocumentType(), LocalDateTime.now());


            Date currDate = new Date();
            String currentDateTimeFileName = new SimpleDateFormat(DATE_TIME_FORMAT).format(currDate);

            String fileName = new StringBuilder(fileLocation).append("/")
                    .append(docProp.getDocumentType()).append("_")
                    .append(currentDateTimeFileName).toString();
            LOGGER.info("Generated file name: {}", fileName);
            String fileNameWithExtn = fileName + "." + docProp.getDocumentFormat().toLowerCase();

            Path path = Paths.get(fileNameWithExtn);
            String fileNameDateTime = currentDateTimeFileName;
            Date newDate = currDate;
            while (Files.exists(path)) {
                LOGGER.info("File is already exists  for fileName: {}", path.toString());
                newDate = DateUtils.addSeconds(newDate, 1);
                fileNameDateTime = new SimpleDateFormat(DATE_TIME_FORMAT).format(newDate);
                fileNameWithExtn = fileNameWithExtn.replace(currentDateTimeFileName, fileNameDateTime);
                path = Paths.get(fileNameWithExtn);
                currentDateTimeFileName = fileNameDateTime;
            }
            LOGGER.info("updating latest file name date time{} in database for document type {}", fileNameDateTime, docProp.getDocumentType());
            batchDAO.updateFileName(docProp.getDocumentType(), fileNameDateTime, LocalDateTime.now());

            Files.write(path, xmlStr.getBytes());
            LOGGER.info("lock released");
            return currentDateTimeFileName;
        } catch (Exception e) {
            LOGGER.error("Error in writing output file");
        }
    }

This method is defined as @Transactional and I am updating the database row in starting to take a row lock so that no other thread can start processing until first one will complete the processing for this. But this is not working.

There is a limitation in the code due to the fileName as only one file can be process in one second but currently I can not change the file naming convention.

ALso, I am looking for some workaround which can work on multiple JVM as I have already applied synchronized block but this will work for single jvm only so looking for database locking which can work on multiple jvm as well.

I am using SQL Server as database and transactional in Micronaut framework.

Do I miss any configuration in database or am I missing something in code?


Solution

  • This is not a database issue, but a issue with your filename on a disk. You have selected a timestamp that is accurate to the second. This timestamp is part of your filename. If two or more threads are executing your code at the same second, all of them will use the exact same timestamp and filename. Therefore only the last thread wins and writes its content to the file.

    As mentioned this is not a locking problem, since the problem is how you store the file content.

    Choose a better filename and use e.g. UUID to make the filename always unique. For example

    Doc_type_1b988f71-4a36-4125-9be1-64b10abe464b
    

    Update 1:

    The actual problem is the filename convention and the missing thread safety of your method writeXMLFile. There are at least the following two problems:

    1. writeXMLFile is not thread safe. Two (or more) simultaneous requests can result in the same filename and will overwrite your XML file on the disk.
    2. The file name convention lowest unit is a second. Therefore your system can actually only accept one request per second. If there are two request within the same second, you run into a problem with the current implementation, since you have to increment your timestamp (second by second).

    To solve 1) you should implement thread safety using synchronized.

    @Transactional
    public synchronized String writeXMLFile(String xmlStr, String fileLocation) { 
       // your code goes here 
    }
    

    To solve 2) you need to rethink the file name convention.

    Generally you only need the thread safety due to the poor file name convention. If you manage to change it, you don't workarounds.