Dans la documentation Spring JDBC, je sais comment insérer un blob à l'aide de JdbcTemplate
final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
jdbcTemplate.execute(
"INSERT INTO lob_table (id, a_blob) VALUES (?, ?)",
new AbstractLobCreatingPreparedStatementCallback(lobhandler) {
protected void setValues(PreparedStatement ps, LobCreator lobCreator)
throws SQLException {
ps.setLong(1, 1L);
lobCreator.setBlobAsBinaryStream(ps, 2, blobIs, (int)blobIn.length());
}
}
);
blobIs.close();
Et aussi comment récupérer la clé générée d'une nouvelle ligne insérée :
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps =
connection.prepareStatement(INSERT_SQL, new String[] {"id"});
ps.setString(1, name);
return ps;
}
},
keyHolder);
// keyHolder.getKey() now contains the generated key
Est-il possible de combiner les deux?
Je suis venu ici pour chercher la même réponse, mais je n'étais pas satisfait de ce qui était accepté. J'ai donc creusé un peu et mis au point cette solution que j'ai testée dans Oracle 10g et 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;
}
cela nécessite également la classe abstraite suivante, basée en partie sur la propriété AbstractLobCreatingPreparedStatementCallback de Spring
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;
}
De plus, la table que vous créez dans Oracle devrait avoir une colonne auto-incrémentée pour l'ID à l'aide d'une séquence et d'un déclencheur. Le déclencheur est nécessaire car sinon, vous devrez utiliser NamedParameterJdbcOperations de Spring (pour effectuer le sequence.nextval dans votre code SQL) qui ne semble pas prendre en charge KeyHolder (que j'utilise pour récupérer l'identifiant automatique). Voir cet article de blog (pas mon blog) pour plus d'informations: http://www.lifftercoffee.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;
/
Tout cela me semblait trop compliqué. Cela fonctionne et est simple. Il utilise 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();
}
}
J'ai fini par exécuter deux requêtes, une pour créer la ligne et une pour mettre à jour le blob.
int id = insertRow();
updateBlob(id, blob);
En regardant le code source de Spring et en extrayant les composants nécessaires, je suis arrivé à ceci:
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);
}
}
);
Je ne peux pas dire que je comprends parfaitement ce qui se passe ici. Je ne sais pas si la méthode compliquée d'extraction de la clé générée est nécessaire dans ce cas simple, et je ne suis pas tout à fait clair quant à l'avantage de même utiliser JdbcTemplate lorsque le code devient aussi poilu.
Quoi qu'il en soit, j'ai testé le code ci-dessus et cela fonctionne. Pour mon cas, j'ai décidé que cela compliquerait trop mon code.
package com.technicalkeeda.dao;
import Java.io.File;
import Java.io.FileInputStream;
import Java.io.FileNotFoundException;
import Java.io.InputStream;
import Java.sql.Types;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.SqlLobValue;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
public class ImageDaoImpl implements ImageDao {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
}
@Override
public void insertImage() {
System.out.println("insertImage" + jdbcTemplate);
try {
final File image = new File("C:\\puppy.jpg");
final InputStream imageIs = new FileInputStream(image);
LobHandler lobHandler = new DefaultLobHandler();
jdbcTemplate.update(
"INSERT INTO trn_imgs (img_title, img_data) VALUES (?, ?)",
new Object[] {
"Puppy",
new SqlLobValue(imageIs, (int)image.length(), lobHandler),
},
new int[] {Types.VARCHAR, Types.BLOB});
} catch (DataAccessException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
En 2012, SimpleJdbcTemplate
est obsolète. C'est ce que j'ai fait:
KeyHolder keyHolder = new GeneratedKeyHolder();
List<SqlParameter> declaredParams = new ArrayList<>();
declaredParams.add(new SqlParameter(Types.VARCHAR));
declaredParams.add(new SqlParameter(Types.BLOB));
declaredParams.add(new SqlParameter(Types.VARCHAR));
declaredParams.add(new SqlParameter(Types.INTEGER));
declaredParams.add(new SqlParameter(Types.INTEGER));
PreparedStatementCreatorFactory pscFactory =
new PreparedStatementCreatorFactory(SQL_CREATE_IMAGE, declaredParams);
pscFactory.setReturnGeneratedKeys(true);
getJdbcTemplate().update(
pscFactory.newPreparedStatementCreator(
new Object[] {
image.getName(),
image.getBytes(),
image.getMimeType(),
image.getHeight(),
image.getWidth()
}), keyHolder);
image.setId(keyHolder.getKey().intValue());
Le SQL ressemble à ceci:
INSERT INTO image (name, image_bytes, mime_type, height, width) VALUES (?, ?, ?, ?, ?)
Si votre base de données sous-jacente est mysql, vous pouvez générer automatiquement votre clé primaire. Ensuite, pour insérer un enregistrement dans votre base de données, vous pouvez utiliser la syntaxe suivante pour l'insertion:
INSERT INTO lob_table (a_blob) VALUES (?)
Une autre solution avec lambda (qui n’est pas requise):
jdbcTemplate.update(dbcon -> {
PreparedStatement ps = dbcon.prepareStatement("INSERT INTO ...");
ps.setString(1, yourfieldValue);
ps.setBinaryStream(2, yourInputStream, yourInputStreamSizeAsInt));
return ps;
});
NB Désolé, cela n'inclut pas KeyGenerator.
Ceci n’est testé que sur MySQL et j’ai seulement collé la partie appropriée. Après avoir exécuté ma classe de test, le résultat est présenté ci-dessous: "Enregistrement ajouté via template.update (psc, kh): 1 ajouté et obtenu clé 36 "
final byte[] bytes = "My Binary Content".getBytes();
final ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
PreparedStatementCreator psc = new PreparedStatementCreator() {
PreparedStatement ps = null;
public PreparedStatement createPreparedStatement(
Connection connection) throws SQLException {
dummy.setStringCode("dummy_jdbc_spring_createPS_withKey_lob");
ps = connection
.prepareStatement(
"INSERT INTO DUMMY (dummy_code, dummy_double, dummy_date, dummy_binary) VALUES (?, ?, ?,?)",
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, dummy.getStringCode());
ps.setDouble(2, dummy.getDoubleNumber());
ps.setDate(3, dummy.getDate());
new DefaultLobHandler().getLobCreator().setBlobAsBinaryStream(
ps, 4, bais, bytes.length);
return ps;
}
};
KeyHolder holder = new GeneratedKeyHolder();
System.out.println("record added via template.update(psc,kh): "
+ template.update(psc, holder)+" added and got key " + holder.getKey());