Проверить, существуют ли данные в базе данных, и вставить новые, если они не найдены с использованием нескольких потоков?

Мое требование примерно такое:
Я написал код, который проверяет данные в базе данных, и если данные не найдены, он использует сторонний API для отдыха для получения данных. Выполняется некоторая логика и окончательно полученные данные сохраняются в базе данных.

 public void exampleMethod(){

      MyObject myObj = getObjFromDatabase(); //getting from db

      if (myObj==null){                       //not found in db
        myObj = getObjFromThirdParty();      //consuming rest api
        //some logic here in case myobj is not in db.... 
      }else{
       //some other logic here in case myobj is in db...
      } 
     saveObjInDatabase(myObj);            //saving it in database
 } 

Мне нужно, чтобы он был сохранен в базе данных только один раз. Получение ответа от стороннего API занимает некоторое время, и этот метод выполняется из нескольких потоков. Теперь проблема в том, что мне нужно, чтобы он был сохранен в базе данных только один раз, но прежде чем один поток сможет сохранить данные в базе данных, другой поток получит значение null из базы данных, логика, которая должна выполняться только тогда, когда «данные не в базе данных» выполняется и сохраняется одни и те же данные более одного раза. (Я использую mongoDB для хранения данных) Как я могу решить эту проблему? Спасибо.

Короткий ответ - «запирание». Я бы сказал, что вам, вероятно, следует выполнить блокировку на стороне базы данных, чтобы она работала, если у вас когда-либо будет несколько JVM, запускающих этот код одновременно с одной и той же базой данных. Будет ли это так? В противном случае вы можете просто использовать блокировку в своем Java-коде.

xtratic 01.05.2018 14:57

Контекст не ясен, но похоже, что несколько потоков создают myObj, где myObj каждого потока - это просто отдельный экземпляр объекта одно и тоже. Проще всего было бы синхронизировать exampleMethod().

Andrew S 01.05.2018 15:02

Синхронизация exampleMethod(), вероятно, является самым простым решением, но если существует много одновременных чтений, это может заблокировать много, и в этом нет необходимости. Потокам нужно только остановиться и подождать, если другой поток обновляет значение.

xtratic 01.05.2018 15:08
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
107
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы спрашиваете о кешировании. Вот пример, который должен прилично работать. Он синхронизируется на getObj в случае необходимости загрузки объекта. Если объект свежий, то getObj возвращается очень быстро и почти не блокирует другие потоки, но если ему нужно загрузить объект, другие потоки будут ждать, пока объект не загрузится.

public class Test {

    // used when you want to refresh the cached object
    private static boolean refreshNext = false;

    // a static reference to your object that all threads can use
    private static MyObj cachedObj = null;

    private static void message(String msg) {
        System.out.println(
                System.currentTimeMillis() + " : " + Thread.currentThread().getName() + " : " + msg);
    }

    private static void sleep(long milli) {
        try { Thread.sleep(milli); } catch (Exception e) { }
    }

    // represents the object stored in the db
    private static MyObj myObjInDb = null;

    private static void saveObjInDb(MyObj obj) {
        // TODO do real saving to db and handle errors
        message("storing in db...");
        myObjInDb = obj;
    }

    private static MyObj loadObjFromDb() {
        // TODO do real loading from db and handle errors
        message("getting from db...");
        sleep(1000);
        return myObjInDb;
    }

    private static MyObj loadObjFromVendor() {
        // TODO do real fetching from vendor and handle errors
        message("getting from vendor...");
        sleep(2000);
        return new MyObj();
    }

    private static MyObj loadObj() {
        message("loading object...");
        MyObj obj = loadObjFromDb();
        if (obj == null) {
            message("db didn't have it.");
            obj = loadObjFromVendor();
            saveObjInDb(obj);
        }
        return obj;
    }

    /** Returns the object, first loading and caching if needed. */
    public static synchronized MyObj getObj() {
        // only one thread can get the object at a time, in case it needs to be loaded. 
        if (cachedObj == null || refreshNext) {
            // load and cache the object
            cachedObj = loadObj();
            refreshNext = false;
        }
        return cachedObj;
    }

    public static void exampleMethod() {
        MyObj obj = getObj();
        message(obj.toString());
        // ... do stuff with obj
    }

    private static class MyObj {
        public final String data = "I have data!";
        @Override public String toString() { return data; }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++)
            new Thread(Test::exampleMethod).start();
    }
}

Другие вопросы по теме