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

发布于 2024-11-30 20:40:45 字数 4991 浏览 0 评论 0原文

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

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

我使用 Blobstore 存储图像(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 + "\"}";

I have a Java Google App Engine web application that allows user upload of images. Locally, it works great. However, once I deploy it to "the cloud", and I upload an image, I get the following error:

java.lang.IllegalArgumentException: can't operate on multiple entity groups in a single transaction.

I use the blobstore to store the images (Blobstore Reference). My method is below:

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

You'll notice that this method first calls "imageService.saveImageToBlobStore". This is what actually saves the bytes of the image. The method "imageService.save" takes the generated blobKey and wraps it in an ImageFile object, which is an object that contains a String blobKey. My website references imageFile.blobKey to get the correct image to display. The "saveImageToBlobStore" looks like this:

@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();
}

My other save method looks like this:

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

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

    datastore.put(imageFileEntity);
}

Like I said before, it works locally, but not deployed. The error is on the call to saveImageToBlobstore, specifically on the "fileservice.getBlobKey(file)". Commenting out this line eliminates the error, but I need this line to save the bytes of the image to the blob store.

I also tried commenting other lines (see below), with no luck. Same error for this:

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

Any ideas? I am using GAE 1.5.2. Thank you!

UPDATE, SOLUTION FOUND:
I took some code out of the transactional "saveImageToBlobStore" and moved it up a level. See below:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

撩心不撩汉 2024-12-07 20:40:45

来自文档

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

还:

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

因此,您必须选择:

  • 在一种极端情况下,您可以创建一个“根”实体(没有任何父实体),并将其设置为其他所有实体的父实体。您将能够以任何您想要的方式使用交易;但每个实体组每秒可以发生的操作数是有限制的。此策略限制了您的可扩展性。

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

  • 中间点需要一些规划:思考哪些流程会因包含在事务中而受益。然后定义哪些实体将参与交易。然后确保在创建时将所有这些放入同一个组中。

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

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

From the Docs:

When the application creates an entity, it can assign another entity as the parent of the new entity. Assigning a parent to a new entity puts the new entity in the same entity group as the parent entity.

Also:

All datastore operations in a transaction must operate on entities in the same entity group.

So, you have to choose:

  • at one extreme, you can create a single 'root' entity (without any parent) set this as parent to everything else. You'll be able to use transactions in any way you want; but there's a limit on how many operations per second can happen on each entity group. This strategy limits your scalability.

  • At the other extreme, you can put every entity alone on it's own group. Simply make every entity a root entity, that is, without any parent. This gets you the maximum scalability, since the underlying system is free to distribute the load evenly among a great number of machines. Unfortunately, this means you wouldn't be able to use transactions. You'll have to think carefully about concurrent consistency and avoid race conditions.

  • The middle point requires some planning: think what processes would benefit from being enclosed in transactions. Then define what entities would participate on the transaction. Then be sure to put all these into the same group when created.

A simple example is to use an entity group for each user; the main user record would be a root entity, and everything else that is related to this user would indicate that root entity as it's parent. With this design, any intra-user operation can be enclosed into a transaction; but any inter-user operation wouldn't.

That seems overly restricting, but you can design your way out. For example, you could define any user-user relation into a new entity group, not belonging to either of the user's groups. Or use batch processing for everything that would cross between entity groups; if you're out of the HTTP request/response, you can control concurrency and so avoid many kinds of race conditions.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文