Google App Engine:本地与部署的行为不同,错误:无法在单个事务中对多个实体组进行操作

时间:2011-08-22 03:45:54

标签: java google-app-engine blobstore

我有一个Java Google App Engine Web应用程序,允许用户上传图像。在本地,它很棒。但是,一旦我将其部署到“云”,并上传了一张图片,我就会收到以下错误:

java.lang.IllegalArgumentException:无法在单个事务中对多个实体组进行操作。

我使用blobstore存储图像(Blobstore Reference)。我的方法如下:

    @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 个答案:

答案 0 :(得分:2)

来自the Docs

  

当应用程序创建实体时,它可以将另一个实体指定为新实体的父实体。将父实体分配给新实体会将新实体放在与父实体相同的实体组中。

此外:

  

事务中的所有数据存储区操作都必须对同一实体组中的实体进行操作。

所以,你必须选择:

  • 在一个极端,您可以创建一个“根”实体(没有任何父级)将其设置为其他所有内容的父级。您将能够以任何您想要的方式使用交易;但是每个实体组每秒可以进行多少次操作是有限制的。这种策略限制了您的可扩展性。

  • 在另一个极端,您可以将每个实体单独放在它自己的组中。简单地使每个实体成为根实体,即没有任何父实体。这为您提供了最大的可扩展性,因为底层系统可以在大量计算机之间平均分配负载。不幸的是,这意味着您将无法使用交易。你必须仔细考虑并发一致性并避免竞争条件。

  • 中间点需要一些规划:考虑哪些流程可以从封闭在交易中受益。然后定义将在事务中参与的实体。然后确保在创建时将所有这些放入同一组中。

一个简单的例子是为每个用户使用一个实体组;主用户记录将是根实体,与此用户相关的所有其他内容将指示根实体作为其父实体。通过这种设计,任何用户内操作都可以包含在一个事务中;但任何用户间操作都不会。

这似乎过于严格,但你可以设计自己的出路。例如,您可以将任何用户 - 用户关系定义为新实体组,而不属于任一用户组。或者对实体组之间交叉的一切使用批处理;如果你没有HTTP请求/响应,你可以控制并发性,从而避免多种竞争条件。