问题
I'm creating a POST service that allows an admin-user to upload images to a Play! application, and while I'm testing it I am using the inbuilt H2 filesystem database. According to
https://gist.github.com/1256286#file-picture-java-L3
a good way to store these images in Play! is as a byte array, which is then rendered using Controller#renderBinary(...)
.
The Campaign
object in this function stores all the data about a campaign and has a list of Banner
objects, each of which is basically an image file and some metadata. The following method under a custom Controller
is supposed to receive images for upload:
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));
}
I'm waiting for another developer to complete the upload form so I am testing this using curl:
curl http://[ourserver]:9000/TemplatePopulationController/putCampaignBanner -i -F campaignPrimaryId=44 -F name=headerBanner -F bannerFile=@tempimages/pissybiscuit.png
Obviously I need to store the image. How can I prevent the PersistenceException?
UPDATE
I have tried adding the @Lob annotation to the byte[]
and also changing the argument type to Blob
and persisting the object thus. Neither has worked.
Examining the logs showed that the server experienced a GenericJDBCException with:
Caused by: org.h2.jdbc.JdbcBatchUpdateException: Hexadecimal string with odd number of characters: "c73e69c8-3443-489a-b5f0-ef40af99673f|image/jpeg";
The log for an instance of the error is:
@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
回答1:
This was caused by a problem in the interaction between Play and H2. I am indebted to this article for part of the answer:
http://www.lunatech-research.fr/playframework-file-upload-blob
It seems Play! doesn't actually put a Blob
object into the database as a Binary Large OBject, but instead stores it as a file in a folder called "attachments" (whose path is configurable). The files in this folder all have UUIDs as filenames, and in fact one I looked at rendered as a .png
I attempted to upload through my service. What Play! then stores in the database is the filename and content type, eg
c73e69c8-3443-489a-b5f0-ef40af99673f|image/jpeg
Here, though, c73e69c8-3443-489a-b5f0-ef40af99673f
is not a hexadecimal number but a filename. H2 may be assuming it is a hexadecimal number and trying to convert it, or Play! may be invoking H2 such that it expects a hexadecimal number stored as a String
. Either way, Play! isn't actually storing a Blob
as a SQL BLOB, and the String
it is trying to store instead is being interpreted as a hexadecimal value.
Caused by: org.h2.jdbc.JdbcBatchUpdateException: Hexadecimal string with odd number of characters: "c73e69c8-3443-489a-b5f0-ef40af99673f|image/jpeg";
Switching the Play! database to a MySQL database (which was planned post-testing anyway) made it work. I am not sure if this can be fixed while still using H2 without actually rewriting H2 or Play, but if anyone knows a fix I will mark that as the answer.
来源:https://stackoverflow.com/questions/14197779/uploading-a-file-in-java-play-1-2-3-then-storing-file-as-byte-array-causes-persi