Spring JdbcTemplate - Insert blob and return generated key
All of this seemed way too complicated to me. This works and is simple. It uses org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.support.SqlLobValue;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
public void setBlob(Long id, byte[] bytes) {
try {
jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("id", id);
parameters.addValue("blob_field", new SqlLobValue(new ByteArrayInputStream(bytes), bytes.length, new DefaultLobHandler()), OracleTypes.BLOB);
jdbcTemplate.update("update blob_table set blob_field=:blob_field where id=:id", parameters);
} catch(Exception e) {
e.printStackTrace();
}
}
I ended up just performing two queries, one to create the row and one to update the blob.
int id = insertRow();
updateBlob(id, blob);
Looking at the Spring source code and extracting the needed parts, I came up with this:
final KeyHolder generatedKeyHolder = new GeneratedKeyHolder();
getJdbcTemplate().execute(
"INSERT INTO lob_table (blob) VALUES (?)",
new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
LobCreator lobCreator = lobHandler.getLobCreator();
lobCreator.setBlobAsBinaryStream(ps, 2, blobIs, (int)blobIn.length());
int rows = ps.executeUpdate();
List generatedKeys = generatedKeyHolder.getKeyList();
generatedKeys.clear();
ResultSet keys = ps.getGeneratedKeys();
if (keys != null) {
try {
RowMapper rowMapper = new ColumnMapRowMapper();
RowMapperResultSetExtractor rse = new RowMapperResultSetExtractor(rowMapper, 1);
generatedKeys.addAll((List) rse.extractData(keys));
}
finally {
JdbcUtils.closeResultSet(keys);
}
}
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows + " rows and returned " + generatedKeys.size() + " keys");
}
return new Integer(rows);
}
}
);
I can't say I fully understand what is going on here. I'm not sure if the complicated method to extract the generated key is necessary in this simple case, and I'm not entirely clear about the benefit of even using JdbcTemplate when the code gets this hairy.
Anyway, I tested the above code and it works. For my case, I decided it would complicate my code too much.
I came here looking for the same answer, but wasn't satisfied with what was accepted. So I did a little digging around and came up with this solution that I've tested in Oracle 10g and Spring 3.0
public Long save(final byte[] blob) {
KeyHolder keyHolder = new GeneratedKeyHolder();
String sql = "insert into blobtest (myblob) values (?)"; //requires auto increment column based on triggers
getSimpleJdbcTemplate().getJdbcOperations().update(new AbstractLobPreparedStatementCreator(lobHandler, sql, "ID") {
@Override
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException {
lobCreator.setBlobAsBytes(ps, 1, blob);
}
}, keyHolder);
Long newId = keyHolder.getKey().longValue();
return newId;
}
this also requires the following abstract class, based in part on Spring's AbstractLobCreatingPreparedStatementCallback
public abstract class AbstractLobPreparedStatementCreator implements PreparedStatementCreator {
private final LobHandler lobHandler;
private final String sql;
private final String keyColumn;
public AbstractLobPreparedStatementCreator(LobHandler lobHandler, String sql, String keyColumn) {
this.lobHandler = lobHandler;
this.sql = sql;
this.keyColumn = keyColumn;
}
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(sql, new String[] { keyColumn });
LobCreator lobCreator = this.lobHandler.getLobCreator();
setValues(ps, lobCreator);
return ps;
}
protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException;
}
Also, the table you create in Oracle should have an auto-incremented column for the id using a sequence and trigger. The trigger is necessary because otherwise you'd have to use Spring's NamedParameterJdbcOperations (to do the sequence.nextval in your SQL) which doesn't seem to have support for KeyHolder (which I use to retrieve the auto-gen id). See this blog post (not my blog) for more info: http://www.lifeaftercoffee.com/2006/02/17/how-to-create-auto-increment-columns-in-oracle/
create table blobtest (
id number primary key,
myblob blob);
create sequence blobseq start with 1 increment by 1;
CREATE OR REPLACE TRIGGER blob_trigger
BEFORE INSERT
ON blobtest
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT blobseq.nextval INTO :NEW.ID FROM dual;
end;
/