在 Java Play 1.2.3 中上传文件然后将文件存储为字节数组会导致 PersistenceException

Posted

技术标签:

【中文标题】在 Java Play 1.2.3 中上传文件然后将文件存储为字节数组会导致 PersistenceException【英文标题】:Uploading a file in Java Play 1.2.3 then storing file as byte array causes PersistenceException 【发布时间】:2013-01-07 14:19:42 【问题描述】:

我正在创建一个 POST 服务,允许管理员用户将图像上传到 Play!应用程序,当我测试它时,我正在使用内置的 H2 文件系统数据库。根据

https://gist.github.com/1256286#file-picture-java-L3

在 Play 中存储这些图像的好方法!是一个字节数组,然后使用Controller#renderBinary(...)渲染。

此函数中的Campaign 对象存储有关活动的所有数据,并有一个Banner 对象列表,每个对象基本上都是一个图像文件和一些元数据。自定义Controller 下的以下方法应该接收要上传的图像:

public static void putCampaignBanner (Upload bannerFile) 
    Campaign campaign = findCampaignByIdOrNew();

    if(campaign.banners == null) 
        campaign.banners = new LinkedList<Banner>();
    

    String name = params.get("name", String.class);
    int width = 0;
    int height = 0;
    try 
        BufferedImage image = ImageIO.read(bannerFile.asFile());
        width = image.getWidth();
        height = image.getHeight();
     catch (IOException e) 
        //it's not a picture.  die gracefully.
        e.printStackTrace();
        renderJSON(new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(campaign));
        return;
    
    if(name==null||name.length()==0) 
        //no name.  die gracefully.  like a swan.
        renderJSON(new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(campaign));
        return;
    

    Banner banner=null;

    for(Banner b: campaign.banners) 
        if(b.name.equals(name)) 
            banner = b;
            break;
        
    
    if(banner==null) 
        banner=new Banner();
        banner.name=name;
        banner.campaign = campaign;
        banner.save();
        campaign.banners.add(banner);
    

    //validate file and populate banner

    //__ vv THIS LINE CAUSES THE EXCEPTION__
    banner.file = bannerFile.asBytes();
    //__ ^^ REMOVING THIS LINE PREVENTS THE EXCEPTION__

    banner.contenttype = bannerFile.getContentType();
    banner.width = width;
    banner.height = height;
    banner.url = getRouterUrlWithId("ImageService.getById", banner.id);

    //__ vv THIS LINE THROWS THE EXCEPTION__
    campaign.save();
    banner.save();

    renderJSON(new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create().toJson(campaign));


我正在等待其他开发人员完成上传表单,因此我正在使用 curl 进行测试:

curl http://[ourserver]:9000/TemplatePopulationController/putCampaignBanner -i -F campaignPrimaryId=44 -F name=headerBanner -F bannerFile=@tempimages/pissybiscuit.png

显然我需要存储图像。如何防止 PersistenceException?

更新 我尝试将 @Lob 注释添加到 byte[] 并将参数类型更改为 Blob 并因此保留对象。两者都没有奏效。

检查日志显示服务器遇到了 GenericJDBCException:

Caused by: org.h2.jdbc.JdbcBatchUpdateException: Hexadecimal string with odd number of characters: "c73e69c8-3443-489a-b5f0-ef40af99673f|image/jpeg";

错误实例的日志是:

@6d0npdn4l
Internal Server Error (500) for request POST /TemplatePopulationController/putCampaignBanner

Execution exception (In /app/controllers/TemplatePopulationController.java around line 285)
PersistenceException occured : update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=?

play.exceptions.JavaExecutionException: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=?
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:229)
    at Invocation.HTTP Request(Play!)
Caused by: javax.persistence.PersistenceException: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=?
    at play.db.jpa.JPABase._save(JPABase.java:50)
    at play.db.jpa.GenericModel.save(GenericModel.java:184)
    at controllers.TemplatePopulationController.putCampaignBanner(TemplatePopulationController.java:285)
    at play.mvc.ActionInvoker.invokeWithContinuation(ActionInvoker.java:546)
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:500)
    at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:476)
    at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:471)
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:159)
    ... 1 more
16:06:36,553 INFO  ~ campaignPrimaryId=8
16:06:36,571 WARN  ~ SQL Error: 90003, SQLState: 90003
16:06:36,571 ERROR ~ Hexadecimal string with odd number of characters: "363438fc-f232-401e-9f69-7db499a24ba4|application/octet-stream"; SQL statement: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=? [90003-170]
16:06:36,572 WARN  ~ SQL Error: 90003, SQLState: 90003
16:06:36,572 ERROR ~ Hexadecimal string with odd number of characters: "363438fc-f232-401e-9f69-7db499a24ba4|application/octet-stream"; SQL statement: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=? [90003-170]
16:06:36,572 ERROR ~ Could not synchronize database state with session org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
    at org.hibernate.exception.SQLStateConverter.handledNonSpecificException (SQLStateConverter.java:140)
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:128)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:185)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:345)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    at org.hibernate.ejb.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:795)
    at play.db.jpa.JPABase._save(JPABase.java:47)
    at play.db.jpa.GenericModel.save(GenericModel.java:184)
    at controllers.TemplatePopulationController.putCampaignBanner(TemplatePopulationController.java:285)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at play.mvc.ActionInvoker.invokeWithContinuation(ActionInvoker.java:546)
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:500)
    at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:476)
    at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:471)
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:159)
    at play.server.PlayHandler$NettyInvocation.execute(PlayHandler.java:220)
    at play.Invoker$Invocation.run(Invoker.java:265)
    at play.server.PlayHandler$NettyInvocation.run(PlayHandler.java:200)
    at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
    at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)
    at java.util.concurrent.FutureTask.run(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(Unknown Source)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)
Caused by: org.h2.jdbc.JdbcBatchUpdateException: Hexadecimal string with odd number of characters: "363438fc-f232-401e-9f69-7db499a24ba4|application/octet-stream"; SQL statement: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=? [90003-170]
    at org.h2.jdbc.JdbcPreparedStatement.executeBatch(JdbcPreparedStatement.java:1121)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.java:1723)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
    ... 29 more
16:06:36,591 ERROR ~

@6d0npdn4m
Internal Server Error (500) for request POST /TemplatePopulationController/putCampaignBanner

Execution exception (In /app/controllers/TemplatePopulationController.java around line 285)
PersistenceException occured : update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=?

play.exceptions.JavaExecutionException: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=?
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:229)
    at Invocation.HTTP Request(Play!)
Caused by: javax.persistence.PersistenceException: update Banner set campaign_id=?, file=?, height=?, name=?, url=?, width=? where id=?
    at play.db.jpa.JPABase._save(JPABase.java:50)
    at play.db.jpa.GenericModel.save(GenericModel.java:184)
    at controllers.TemplatePopulationController.putCampaignBanner(TemplatePopulationController.java:285)
    at play.mvc.ActionInvoker.invokeWithContinuation(ActionInvoker.java:546)
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:500)
    at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:476)
    at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:471)
    at play.mvc.ActionInvoker.invoke(ActionInvoker.java:159)
    ... 1 more

【问题讨论】:

请发布完整的错误消息和所有堆栈跟踪。 我已经添加了我得到的堆栈跟踪。错误页面实际上只是重复了堆栈跟踪中的信息。 这似乎可能相关:lunatech-research.fr/playframework-file-upload-blob 尽管Blob 方法更是如此。当我开始这个时,我忘记了我使用的是不同的方法。 【参考方案1】:

这是由 Play 和 H2 之间的交互问题引起的。我感谢这篇文章的部分答案:

http://www.lunatech-research.fr/playframework-file-upload-blob

看来玩!实际上并没有将Blob 对象作为二进制大对象放入数据库,而是将其作为文件存储在名为“附件”的文件夹中(其路径是可配置的)。此文件夹中的文件都具有 UUID 作为文件名,事实上,我查看了一个呈现为 .png 的文件,我试图通过我的服务上传。玩什么!然后存储在数据库中的是文件名和内容类型,例如

c73e69c8-3443-489a-b5f0-ef40af99673f|image/jpeg

不过,c73e69c8-3443-489a-b5f0-ef40af99673f 不是十六进制数字,而是文件名。 H2 可能会假设它 一个十六进制数并尝试对其进行转换,或者 Play!可能正在调用 H2,使其期望存储为 String 的十六进制数。不管怎样,玩!实际上并没有将 Blob 存储为 SQL BLOB,而是尝试存储的 String 被解释为十六进制值。

Caused by: org.h2.jdbc.JdbcBatchUpdateException: Hexadecimal string with odd number of characters: "c73e69c8-3443-489a-b5f0-ef40af99673f|image/jpeg";

切换播放!数据库到 mysql 数据库(无论如何都计划在测试后)使它工作。我不确定是否可以在仍然使用 H2 而无需实际重写 H2 或 Play 的情况下解决此问题,但如果有人知道修复方法,我会将其标记为答案。

【讨论】:

这绝对不是 H2 中的错误。相反,在使用 H2 时,Play 框架似乎试图将 blob 存储为文本,而不是输入流。 调整了答案以匹配您的评论。不过,我认为尝试将字符串保留为十六进制二进制值,因为它看起来可能是一个错误。

以上是关于在 Java Play 1.2.3 中上传文件然后将文件存储为字节数组会导致 PersistenceException的主要内容,如果未能解决你的问题,请参考以下文章

在 Play Framework 中上传后从临时文件夹中删除文件

如何在play2中获取带有其他输入的上传文件?

错误记录Google Play 上架报错 ( 上传 release 版本 APK 或 AAB )

如何在Play商店上传拆分应用程序二进制文件

在 Play 2.1 和 Scala 中为文件上传编写测试用例

更新到项目后,在play store中替换apk文件