Sind Sie sicher, dass der Transaktionsrat ausgeführt wird? By default, verwendet Spring den "Proxy" -Ratenmodus. Der Transaktionshinweis wird nur ausgeführt, wenn Sie die Springproxied-Instanz Ihrer Ressource mit JAX-RS registriert haben oder wenn Sie "aspectj" -Weben anstelle des Standardberatungsmodus "Proxy" verwendet haben.
Angenommen, eine physical-Transaktion wird nicht als Ergebnis der Transaktionsausbreitung wiederverwendet. Die Verwendung von @Transactional
für diese download() -Methode ist im Allgemeinen falsch.
Wenn der Transaktionshinweis tatsächlich ausgeführt wird, endet die Transaktion beim Zurückkehren von der download() - Methode. Die Blob
Javadoc sagt: "Ein Blob
Objekt ist gültig für die Dauer der Transaktion, in der wurde erstellt wurde." In §16.3.7 der Spezifikation JDBC 4.2 heißt es jedoch: "Blob
, Clob
und Objekte bleiben mindestens für die Dauer der Transaktion, in der sie erstellt werden, gültig." Daher ist die von getBinaryStream() zurückgegebene InputStream
nicht garantiert gültig für die Antwort; Die Gültigkeit hängt von den vom JDBC-Treiber bereitgestellten Garantien ab. Für maximale Portabilität sollten Sie sich darauf verlassen, dass Blob
nur für die Dauer der Transaktion gültig ist.
Unabhängig davon, ob der Transaktionshinweis ausgeführt wird, haben Sie möglicherweise eine Racebedingung, da die zugrunde liegende JDBC-Verbindung, die zum Abrufen der Blob
verwendet wurde, möglicherweise so wiederverwendet wird, dass Blob
ungültig wird.
EDIT: Testing Jersey 2.17, scheint es, dass das Verhalten eines Response
von einem InputStream
der Konstruktion auf die Antwort MIME-Typ angegeben abhängt. In einigen Fällen wird der InputStream
zuerst vollständig in den Speicher gelesen, bevor die Antwort gesendet wird. In anderen Fällen wird der InputStream
zurückgestreamt.
Hier ist mein Testfall:
@Path("test")
public class MyResource {
@GET
public Response getIt() {
return Response.ok(new InputStream() {
@Override
public int read() throws IOException {
return 97; // 'a'
}
}).build();
}
}
Wenn die getIT() -Methode mit @Produces(MediaType.TEXT_PLAIN)
oder keine @Produces
Anmerkung kommentiert wird, dann versucht Jersey die gesamte (unendlich) InputStream
in den Speicher und den Anwendungsserver schließlich zu lesen stürzt ab, weil der Speicher nicht mehr ausreicht. Wenn die Methode getIt() mit @Produces(MediaType.APPLICATION_OCTET_STREAM)
annotiert ist, wird die Antwort zurückgestreamt.
So funktioniert Ihre download() - Methode möglicherweise einfach, weil der Blob nicht zurückgestreamt wird. Jersey könnte den gesamten Blob in Erinnerung behalten.
Verwandte: How to stream an endless InputStream with JAX-RS
EDIT2: Ich habe ein Demonstrationsprojekt mit Spring-Boot und Apache CXF erstellt:
https://github.com/dtrebbien/so30356840-cxf
Wenn Sie das Projekt und führen Sie auf der Kommandozeile:
curl 'http://localhost:8080/myapp/test/data/1' >/dev/null
Dann sehen Sie eine Protokollausgabe wie folgt:
2015-06-01 15:58:14.573 DEBUG 9362 --- [nio-8080-exec-1] org.apache.cxf.transport.http.Headers : Request Headers: {Accept=[*/*], Content-Type=[null], host=[localhost:8080], user-agent=[curl/7.37.1]}
2015-06-01 15:58:14.584 DEBUG 9362 --- [nio-8080-exec-1] org.apache.cxf.jaxrs.utils.JAXRSUtils : Trying to select a resource class, request path : /test/data/1
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] org.apache.cxf.jaxrs.utils.JAXRSUtils : Trying to select a resource operation on the resource class com.sample.resource.MyResource
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] org.apache.cxf.jaxrs.utils.JAXRSUtils : Resource operation getIt may get selected
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] org.apache.cxf.jaxrs.utils.JAXRSUtils : Resource operation getIt on the resource class com.sample.resource.MyResource has been selected
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.j.interceptor.JAXRSInInterceptor : Request path is: /test/data/1
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.j.interceptor.JAXRSInInterceptor : Request HTTP method is: GET
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.j.interceptor.JAXRSInInterceptor : Request contentType is: */*
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.j.interceptor.JAXRSInInterceptor : Accept contentType is: */*
2015-06-01 15:58:14.585 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.j.interceptor.JAXRSInInterceptor : Found operation: getIt
2015-06-01 15:58:14.595 DEBUG 9362 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [com.sample.resource.MyResource.getIt]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2015-06-01 15:58:14.595 DEBUG 9362 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Acquired Connection [ProxyConnection[PooledConnection[[email protected]]]] for JDBC transaction
2015-06-01 15:58:14.596 DEBUG 9362 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [ProxyConnection[PooledConnection[[email protected]]]] to manual commit
2015-06-01 15:58:14.602 DEBUG 9362 --- [nio-8080-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL query
2015-06-01 15:58:14.603 DEBUG 9362 --- [nio-8080-exec-1] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [SELECT data FROM images WHERE id = ?]
2015-06-01 15:58:14.620 DEBUG 9362 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
2015-06-01 15:58:14.620 DEBUG 9362 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [ProxyConnection[PooledConnection[[email protected]]]]
2015-06-01 15:58:14.621 DEBUG 9362 --- [nio-8080-exec-1] o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [ProxyConnection[PooledConnection[org.hsqldb.jdbc.JDBCConnect[email protected]]]] after transaction
2015-06-01 15:58:14.621 DEBUG 9362 --- [nio-8080-exec-1] o.s.jdbc.datasource.DataSourceUtils : Returning JDBC Connection to DataSource
2015-06-01 15:58:14.621 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Invoking handleMessage on interceptor [email protected]
2015-06-01 15:58:14.622 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Adding interceptor [email protected] to phase prepare-send
2015-06-01 15:58:14.622 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Adding interceptor [email protected] to phase marshal
2015-06-01 15:58:14.622 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Chain [email protected] was created. Current flow:
prepare-send [MessageSenderInterceptor]
marshal [JAXRSOutInterceptor]
2015-06-01 15:58:14.623 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Invoking handleMessage on interceptor [email protected]
2015-06-01 15:58:14.623 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Adding interceptor org.apache.cxf.inte[email protected]6129236d to phase prepare-send-ending
2015-06-01 15:58:14.623 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Chain [email protected] was modified. Current flow:
prepare-send [MessageSenderInterceptor]
marshal [JAXRSOutInterceptor]
prepare-send-ending [MessageSenderEndingInterceptor]
2015-06-01 15:58:14.623 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Invoking handleMessage on interceptor [email protected]
2015-06-01 15:58:14.627 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.j.interceptor.JAXRSOutInterceptor : Response content type is: application/octet-stream
2015-06-01 15:58:14.631 DEBUG 9362 --- [nio-8080-exec-1] o.apache.cxf.ws.addressing.ContextUtils : retrieving MAPs from context property javax.xml.ws.addressing.context.inbound
2015-06-01 15:58:14.631 DEBUG 9362 --- [nio-8080-exec-1] o.apache.cxf.ws.addressing.ContextUtils : WS-Addressing - failed to retrieve Message Addressing Properties from context
2015-06-01 15:58:14.636 DEBUG 9362 --- [nio-8080-exec-1] o.a.cxf.phase.PhaseInterceptorChain : Invoking handleMessage on interceptor org.apache.cxf.inte[email protected]6129236d
2015-06-01 15:58:14.639 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.t.http.AbstractHTTPDestination : Finished servicing http request on thread: Thread[http-nio-8080-exec-1,5,main]
2015-06-01 15:58:14.639 DEBUG 9362 --- [nio-8080-exec-1] o.a.c.t.servlet.ServletController : Finished servicing http request on thread: Thread[http-nio-8080-exec-1,5,main]
Ich habe die Protokollausgabe zur besseren Lesbarkeit getrimmt. Die wichtige Sache zu beachten ist, dass die Transaktion festgeschrieben wird und die JDBC-Verbindung zurückgegeben wird, bevor die Antwort gesendet wird. Daher ist die InputStream
, die von blob.getBinaryStream()
zurückgegeben wird, nicht unbedingt gültig und die getIt() resource method kann undefiniertes Verhalten aufrufen.
EDIT3: Eine Praxis empfohlen @Transactional
Anmerkung Spring für die Verwendung ist die Service-Methode (siehe Spring @Transactional Annotation Best Practice) mit Anmerkungen zu versehen. Sie könnten eine Servicemethode haben, die den Blob findet und die Blobdaten an die Antwort OutputStream
überträgt. Die Service-Methode könnte mit @Transactional
kommentiert werden, so dass die Transaktion, in der die Blob
erstellt wird, für die Dauer der Übertragung geöffnet bleiben würde. Es scheint mir jedoch, dass dieser Ansatz eine Denial-of-Service-Sicherheitslücke durch eine "slow read" attack einleiten könnte. Da die Transaktion für die Dauer der Übertragung für maximale Portabilität geöffnet bleiben sollte, könnten zahlreiche langsame Leser Ihre Datenbanktabelle (n) sperren, indem sie offene Transaktionen halten.
Ein möglicher Ansatz besteht darin, den Blob in einer temporären Datei zu speichern und die Datei zurückzustreamen. Einige Ideen zum Lesen einer Datei, während sie gleichzeitig geschrieben wird, finden Sie unter How do I use Java to read from a file that is actively being written?. Dieser Fall ist jedoch einfacher, da die Länge des Blobs durch Aufruf der Methode Blob#length() ermittelt werden kann.
§16.3.7 der Spezifikation JDBC 4.2 bestätigt Ihre Interpretation, dass ein 'Blob' außerhalb der Transaktion gültig sein kann (ich habe meine Antwort entsprechend aktualisiert).Beim Lesen von JDBC Developer's Guide (http://docs.oracle.com/database/121/JJDBC/toc.htm) von Oracle sehe ich jedoch keine zusätzlichen Garantien bezüglich der Gültigkeit eines "Blobs" außerhalb der Transaktion in wo es erstellt wurde Ich hätte viele Fragen, beispielsweise was passiert, wenn die Verbindung wiederverwendet wird und die LOB-Daten geändert werden? Was passiert, wenn das LOB gelöscht wird? Funktioniert das nur innerhalb der LOB-Prefetch-Größe? Etc. –