Google App Engine: различное поведение локально и развернуто, ошибка: невозможно работать с несколькими группами объектов в одной транзакции - PullRequest
0 голосов
/ 22 августа 2011

У меня есть веб-приложение 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 (file)».Комментирование этой строки устраняет ошибку, но мне нужна эта строка для сохранения байтов изображения в хранилище BLOB-объектов.

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

@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 + "\"}";

1 Ответ

2 голосов
/ 22 августа 2011

От Документы :

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

Также:

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

Итак, вы должны выбрать:

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...