diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java index 501a44de0..bbe834a8c 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java @@ -2,11 +2,16 @@ package io.ebeaninternal.server.persist; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.SpiProfileTransactionEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.List; /** * A batched statement that is held in BatchedPstmtHolder. It has a list of @@ -17,6 +22,8 @@ import java.util.ArrayList; */ public class BatchedPstmt implements SpiProfileTransactionEvent { + private static final Logger log = LoggerFactory.getLogger(BatchedPstmt.class); + /** * The underlying statement. */ @@ -40,6 +47,8 @@ public class BatchedPstmt implements SpiProfileTransactionEvent { private int[] results; + private List inputStreams; + /** * Create with a given statement. */ @@ -117,16 +126,19 @@ public class BatchedPstmt implements SpiProfileTransactionEvent { } private void executeAndCheckRowCounts() throws SQLException { + try { + results = pstmt.executeBatch(); + if (results.length != list.size()) { + String s = "results array error " + results.length + " " + list.size(); + throw new SQLException(s); + } - results = pstmt.executeBatch(); - if (results.length != list.size()) { - String s = "results array error " + results.length + " " + list.size(); - throw new SQLException(s); - } - - // check for concurrency exceptions... - for (int i = 0; i < results.length; i++) { - list.get(i).checkRowCount(results[i]); + // check for concurrency exceptions... + for (int i = 0; i < results.length; i++) { + list.get(i).checkRowCount(results[i]); + } + } finally { + closeInputStreams(); } } @@ -148,4 +160,23 @@ public class BatchedPstmt implements SpiProfileTransactionEvent { public int[] getResults() { return results; } + + /** + * Register any inputStreams that should be closed after execution. + */ + public void registerInputStreams(List inputStreams) { + this.inputStreams = inputStreams; + } + + private void closeInputStreams() { + if (inputStreams != null) { + for (InputStream inputStream : inputStreams) { + try { + inputStream.close(); + } catch (IOException e) { + log.warn("Error closing inputStream ", e); + } + } + } + } } diff --git a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java index ffb0e6d71..100b2ff21 100644 --- a/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java +++ b/src/main/java/io/ebeaninternal/server/persist/BatchedPstmtHolder.java @@ -39,6 +39,15 @@ public class BatchedPstmtHolder { * This will return null if no matching PreparedStatement is found. */ public PreparedStatement getStmt(String stmtKey, BatchPostExecute postExecute) { + BatchedPstmt batchedPstmt = getBatchedPstmt(stmtKey, postExecute); + return (batchedPstmt == null) ? null : batchedPstmt.getStatement(); + } + + /** + * Return the BatchedPstmt that holds the batched statement. + */ + public BatchedPstmt getBatchedPstmt(String stmtKey, BatchPostExecute postExecute) { + BatchedPstmt bs = stmtMap.get(stmtKey); if (bs == null) { // the PreparedStatement has need been created @@ -53,7 +62,7 @@ public class BatchedPstmtHolder { if (bsSize > maxSize) { maxSize = bsSize; } - return bs.getStatement(); + return bs; } /** diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java b/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java index d74977850..a861385dc 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/DmlHandler.java @@ -46,6 +46,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { */ protected DataBind dataBind; + protected BatchedPstmt batchedPstmt; + protected String sql; /** @@ -273,20 +275,18 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest { /** * Return a prepared statement taking into account batch requirements. */ - protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean request, - boolean genKeys) throws SQLException { + protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean request, boolean genKeys) throws SQLException { BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder(); - PreparedStatement stmt = batch.getStmt(sql, request); - - if (stmt != null) { - return stmt; + batchedPstmt = batch.getBatchedPstmt(sql, request); + if (batchedPstmt != null) { + return batchedPstmt.getStatement(); } - stmt = getPstmt(t, sql, genKeys); + PreparedStatement stmt = getPstmt(t, sql, genKeys); - BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql, t); - batch.addStmt(bs, request); + batchedPstmt = new BatchedPstmt(stmt, genKeys, sql, t); + batch.addStmt(batchedPstmt, request); return stmt; } diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java index 7887c4b5a..babc0ab39 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java @@ -91,7 +91,9 @@ public class InsertHandler extends DmlHandler { } dataBind = bind(pstmt); meta.bind(this, bean, withId, persistRequest.isPublish()); - + if (persistRequest.isBatched()) { + batchedPstmt.registerInputStreams(dataBind.getInputStreams()); + } logSql(sql); } diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java b/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java index 5e4860111..2f1082046 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/UpdateHandler.java @@ -47,7 +47,9 @@ public class UpdateHandler extends DmlHandler { } dataBind = bind(pstmt); meta.bind(persistRequest, this, updatePlan); - + if (persistRequest.isBatched()) { + batchedPstmt.registerInputStreams(dataBind.getInputStreams()); + } setUpdateGenValues(); logSql(sql); diff --git a/src/main/java/io/ebeaninternal/server/type/DataBind.java b/src/main/java/io/ebeaninternal/server/type/DataBind.java index e408bc588..b28be14b7 100644 --- a/src/main/java/io/ebeaninternal/server/type/DataBind.java +++ b/src/main/java/io/ebeaninternal/server/type/DataBind.java @@ -1,8 +1,11 @@ package io.ebeaninternal.server.type; import io.ebeaninternal.server.core.timezone.DataTimeZone; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; @@ -13,10 +16,14 @@ import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Calendar; +import java.util.List; public class DataBind { + private static final Logger log = LoggerFactory.getLogger(DataBind.class); + private final DataTimeZone dataTimeZone; private final PreparedStatement pstmt; @@ -25,6 +32,8 @@ public class DataBind { private final StringBuilder bindLog = new StringBuilder(); + private List inputStreams; + private int pos; public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) { @@ -79,7 +88,24 @@ public class DataBind { } public int executeUpdate() throws SQLException { - return pstmt.executeUpdate(); + try { + return pstmt.executeUpdate(); + } finally { + closeInputStreams(); + } + } + + private void closeInputStreams() { + if (inputStreams != null) { + for (InputStream inputStream : inputStreams) { + try { + inputStream.close(); + } catch (IOException e) { + log.warn("Error closing InputStream that was bound to PreparedStatement", e); + } + } + inputStreams = null; + } } public PreparedStatement getPstmt() { @@ -147,7 +173,19 @@ public class DataBind { pstmt.setString(++pos, String.valueOf(v)); } + /** + * Return any inputStreams that have been bound (and should be closed). + * This is used for batched statement execution only. + */ + public List getInputStreams() { + return inputStreams; + } + public void setBinaryStream(InputStream inputStream, long length) throws SQLException { + if (inputStreams == null) { + inputStreams = new ArrayList<>(); + } + inputStreams.add(inputStream); pstmt.setBinaryStream(++pos, inputStream, length); } diff --git a/src/test/java/org/tests/types/TestFileType.java b/src/test/java/org/tests/types/TestFileType.java index 762536198..85d3d2173 100644 --- a/src/test/java/org/tests/types/TestFileType.java +++ b/src/test/java/org/tests/types/TestFileType.java @@ -2,18 +2,107 @@ package org.tests.types; import io.ebean.BaseTestCase; import io.ebean.Ebean; -import org.tests.model.types.SomeFileBean; +import io.ebean.Transaction; import org.junit.Test; +import org.tests.model.types.SomeFileBean; import java.io.File; +import java.io.IOException; +import java.io.PrintStream; import java.net.URL; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; public class TestFileType extends BaseTestCase { - File file = getFile("/profile-image.jpg"); - File file2 = getFile("/java-64.png"); + private File file = getFile("/profile-image.jpg"); + private File file2 = getFile("/java-64.png"); + + private File newTempFile() throws IOException { + File tempFile = File.createTempFile("testfile", "txt"); + try (PrintStream ps = new PrintStream(tempFile)) { + ps.println("Hello World!"); + } + return tempFile; + } + + @Test + public void test_closeFileStreamUnbatched() throws IOException { + + File tempFile = newTempFile(); + + SomeFileBean bean0 = new SomeFileBean(); + bean0.setName("tempBeanUnbatched"); + bean0.setContent(tempFile); + Ebean.save(bean0); + + assertTrue(tempFile.delete()); + } + + @Test + public void test_closeFileStreamUnbatched_onUpdate() throws IOException { + + SomeFileBean bean0 = new SomeFileBean(); + bean0.setName("tempBeanUnbatched"); + bean0.setContent(newTempFile()); + Ebean.save(bean0); + + + File updateFile = newTempFile(); + bean0.setName("tempBeanModified"); + bean0.setContent(updateFile); + Ebean.save(bean0); + + assertTrue(updateFile.delete()); + } + + @Test + public void test_closeFileStreamBatched() throws IOException { + + SomeFileBean bean0 = new SomeFileBean(); + bean0.setName("tempBeanUnbatched"); + bean0.setContent(newTempFile()); + Ebean.save(bean0); + + + File tempFile = newTempFile(); + + try (Transaction txn = Ebean.beginTransaction()) { + txn.setBatchSize(30); + txn.setBatchMode(true); + + bean0.setName("tempBeanBatchedModified"); + bean0.setContent(tempFile); + Ebean.save(bean0); + + txn.commit(); + } + + assertTrue(tempFile.delete()); + } + + @Test + public void test_closeFileStreamBatched_update() throws IOException { + + File tempFile = newTempFile(); + + try (Transaction txn = Ebean.beginTransaction()) { + txn.setBatchSize(30); + txn.setBatchMode(true); + + SomeFileBean bean0 = new SomeFileBean(); + bean0.setName("tempBeanBatched"); + bean0.setContent(tempFile); + Ebean.save(bean0); + + txn.commit(); + } + + assertTrue(tempFile.delete()); + } @Test public void test_insertNullFile() {