Google App Engine: локальное поведение отличается от развернутого, ошибка: невозможно работать с несколькими группами сущностей в одной транзакции

У меня есть веб-приложение Java Google App Engine, которое позволяет пользователям загружать изображения. Локально работает отлично. Однако, как только я развертываю его в «облаке» и загружаю изображение, я получаю следующую ошибку:

java.lang.IllegalArgumentException: нельзя работать с несколькими группами сущностей в одной транзакции.

Я использую хранилище BLOB-объектов для хранения изображений (справочник Blobstore ). Мой метод ниже:

    @RequestMapping(value = "/ajax/uploadWelcomeImage", method = RequestMethod.POST)
@ResponseBody
public String uploadWelcomeImage(@RequestParam("id") long id,
        HttpServletRequest request) throws IOException, ServletException {

    byte[] bytes = IOUtils.toByteArray(request.getInputStream());
    Key parentKey = KeyFactory.createKey(ParentClass.class.getSimpleName(),
            id);
    String blobKey = imageService.saveImageToBlobStore(bytes);
    imageService.save(blobKey, parentKey);

    return "{success:true, id:\"" + blobKey + "\"}";
}

Вы заметите, что этот метод сначала вызывает «imageService.saveImageToBlobStore». Это то, что на самом деле сохраняет байты изображения. Метод "imageService.save" берет сгенерированный blobKey и заключает его в объект ImageFile, который является объектом, содержащим String blobKey. Мой веб-сайт ссылается на imageFile.blobKey, чтобы получить правильное изображение для отображения. "saveImageToBlobStore" выглядит так:

@Transactional
public String saveImageToBlobStore(byte[] bytes) {
    // Get a file service
    FileService fileService = FileServiceFactory.getFileService();

    // Create a new Blob file with mime-type "text/plain"
    AppEngineFile file = null;
    try {
        file = fileService.createNewBlobFile("image/jpeg");
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Open a channel to write to it
    boolean lock = true;
    FileWriteChannel writeChannel = null;
    try {
        writeChannel = fileService.openWriteChannel(file, lock);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (FinalizationException e) {
        e.printStackTrace();
    } catch (LockException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // This time we write to the channel using standard Java
    try {
        writeChannel.write(ByteBuffer.wrap(bytes));
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now finalize
    try {
        writeChannel.closeFinally();
    } catch (IllegalStateException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    // Now read from the file using the Blobstore API
    BlobKey blobKey = fileService.getBlobKey(file);
    while (blobKey == null) { //this is hacky, but necessary as sometimes the blobkey isn't available right away
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        blobKey = fileService.getBlobKey(file);
    }

    // return
    return blobKey.getKeyString();
}

Мой другой метод сохранения выглядит так:

public void save(String imageFileBlobKey, Key parentKey) {
    DatastoreService datastore = DatastoreServiceFactory
            .getDatastoreService();

    Entity imageFileEntity = new Entity("ImageFile", parentKey);
    imageFileEntity.setProperty("blobKey", imageFileBlobKey);

    datastore.put(imageFileEntity);
}

Как я уже говорил, он работает локально, но не развернут. Ошибка возникает при вызове saveImageToBlobstore, в частности, в «fileservice.getBlobKey (файл)». Комментирование этой строки устраняет ошибку, но мне нужна эта строка, чтобы сохранить байты изображения в хранилище больших двоичных объектов.

Я также пытался комментировать другие строки (см. ниже), но безуспешно. Та же ошибка для этого:

@RequestMapping(value = "/ajax/uploadWelcomeImage", method = RequestMethod.POST)
@ResponseBody
public String uploadWelcomeImage(@RequestParam("id") long id,
        HttpServletRequest request) throws IOException, ServletException {

    //byte[] bytes = IOUtils.toByteArray(request.getInputStream());
    //Key parentKey= KeyFactory.createKey(ParentClass.class.getSimpleName(),
            //id);
    byte[] bytes = {0,1,0};
    String blobKey = imageService.saveImageToBlobStore(bytes);
    //imageService.save(blobKey, parentKey);

    return "{success:true, id:\"" + blobKey + "\"}";
}

Любые идеи? Я использую GAE 1.5.2. Спасибо!

ОБНОВЛЕНИЕ, НАЙДЕНО РЕШЕНИЕ: я взял некоторый код из транзакционного "saveImageToBlobStore" и переместил его на уровень выше. Увидеть ниже:

@RequestMapping(value = "/ajax/uploadWelcomeImage", method = RequestMethod.POST)
@ResponseBody
public String uploadWelcomeImage(@RequestParam("id") long id,
        HttpServletRequest request) throws IOException, ServletException {

    byte[] bytes = IOUtils.toByteArray(request.getInputStream());
    Key parentKey = KeyFactory.createKey(ParentClass.class.getSimpleName(),
            id);

            //pulled the following out of transactional method:
    AppEngineFile file = imageService.saveImageToBlobStore(bytes);
    FileService fileService = FileServiceFactory.getFileService();
            //code below is similar to before//////////////
    BlobKey key = fileService.getBlobKey(file);
    String keyString = key.getKeyString();
    imageService.save(keyString, parentKey);

    return "{success:true, id:\"" + keyString + "\"}";

person Jared    schedule 22.08.2011    source источник


Ответы (1)


Из Документов:

Когда приложение создает объект, оно может назначить другой объект в качестве родителя нового объекта. Назначение родителя новой сущности помещает новую сущность в ту же группу сущностей, что и родительская сущность.

Также:

Все операции хранилища данных в транзакции должны выполняться над сущностями в одной группе сущностей.

Итак, вам предстоит выбрать:

  • с одной стороны, вы можете создать один «корневой» объект (без какого-либо родителя), установить его в качестве родителя для всего остального. Вы сможете использовать транзакции любым удобным для вас способом; но существует ограничение на количество операций в секунду, которые могут выполняться в каждой группе сущностей. Эта стратегия ограничивает вашу масштабируемость.

  • С другой стороны, вы можете поместить каждую сущность в отдельную группу. Просто сделайте каждую сущность корневой сущностью, то есть без какого-либо родителя. Это обеспечивает максимальную масштабируемость, поскольку базовая система может равномерно распределять нагрузку между большим количеством машин. К сожалению, это означает, что вы не сможете использовать транзакции. Вам придется тщательно продумать одновременную согласованность и избегать условий гонки.

  • Средняя точка требует некоторого планирования: подумайте, какие процессы выиграют от включения в транзакции. Затем определите, какие сущности будут участвовать в транзакции. Затем обязательно поместите все это в одну группу при создании.

Простой пример — использовать группу сущностей для каждого пользователя; запись основного пользователя будет корневым объектом, а все остальное, что связано с этим пользователем, будет указывать этот корневой объект как родительский. При таком дизайне любая внутрипользовательская операция может быть заключена в транзакцию; но любая межпользовательская операция не будет.

Это кажется чрезмерным ограничением, но вы можете придумать свой выход. Например, вы можете определить любое отношение «пользователь-пользователь» в новую группу сущностей, не принадлежащую ни к одной из групп пользователя. Или используйте пакетную обработку для всего, что пересекается между группами сущностей; если вы находитесь вне HTTP-запроса/ответа, вы можете контролировать параллелизм и, таким образом, избегать многих видов условий гонки.

person Javier    schedule 22.08.2011
comment
Спасибо! Я отредактировал свой вопрос, чтобы показать, как я его решил. - person Jared; 22.08.2011