Spring JdbcTemplate - 插入 blob 并返回生成的密钥

Posted

技术标签:

【中文标题】Spring JdbcTemplate - 插入 blob 并返回生成的密钥【英文标题】:Spring JdbcTemplate - Insert blob and return generated key 【发布时间】:2011-02-15 18:51:21 【问题描述】:

从 Spring JDBC 文档中,我知道如何insert a blob using 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();

还有如何retrieve the generated key of a newly inserted row:

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

有没有办法将两者结合起来?

【问题讨论】:

【参考方案1】:

我来到这里寻找相同的答案,但对接受的内容不满意。因此,我进行了一些挖掘,并提出了我在 Oracle 10g 和 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;

这还需要以下抽象类,部分基于 Spring 的 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;

此外,您在 Oracle 中创建的表应该有一个使用序列和触发器的 id 自动递增列。触发器是必要的,因为否则您必须使用 Spring 的 NamedParameterJdbcOperations(在 SQL 中执行 sequence.nextval),它似乎不支持 KeyHolder(我用它来检索自动生成 ID)。有关更多信息,请参阅此博文(不是我的博客):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;
/

【讨论】:

接受这个答案,因为所有的赞成票。您应该将 getSimpleJdbcTemplate().getJdbcOperations() 替换为 getJdbcTemplate(),因为 SimpleJdbcTemplate 现在已被弃用。【参考方案2】:

所有这些对我来说都太复杂了。这很有效而且很简单。它使用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();
        
    

【讨论】:

问题是关于 inserting 带有 blob 的 new 行并取回新生成的密钥。您的代码更新 existing 行,其中包含一个blob。我可以假设您的意思是作为第一步仅插入没有 blob 的行,然后执行此操作吗?【参考方案3】:

我最终只执行了两个查询,一个用于创建行,一个用于更新 blob。

int id = insertRow();
updateBlob(id, blob);

查看Spring源代码并提取所需部分,我想出了这个:

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);
        
    
);

我不能说我完全理解这里发生了什么。我不确定在这种简单的情况下是否需要使用复杂的方法来提取生成的密钥,而且当代码变得如此繁琐时,我也不完全清楚使用 JdbcTemplate 的好处。

无论如何,我测试了上面的代码并且它可以工作。就我而言,我认为这会使我的代码过于复杂。

【讨论】:

【参考方案4】:
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();
        

    

【讨论】:

您插入了一个 BLOB,但没有得到生成的密钥。【参考方案5】:

在 2012 年,SimpleJdbcTemplate 已弃用。这就是我所做的:

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());

SQL 如下所示:

INSERT INTO image (name, image_bytes, mime_type, height, width) VALUES (?, ?, ?, ?, ?)

【讨论】:

【参考方案6】:

如果您的底层数据库是 mysql,您可以自动生成主键。然后要将记录插入到您的数据库中,您可以使用以下语法进行插入:

INSERT INTO lob_table (a_blob) VALUES (?)

【讨论】:

【参考方案7】:

这仅在 MySql 上进行了测试,我只粘贴了相关部分。 运行我的测试类后,结果如下所示: "通过 template.update(psc,kh) 添加的记录:添加 1 并获得密钥 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());

【讨论】:

【参考方案8】:

使用 lambda 的另一种解决方案(不是必需的):

jdbcTemplate.update(dbcon -> 
    PreparedStatement ps = dbcon.prepareStatement("INSERT INTO ...");
    ps.setString(1, yourfieldValue);
    ps.setBinaryStream(2, yourInputStream, yourInputStreamSizeAsInt));
    return ps;
);

注意。抱歉,这不包括 KeyGenerator。

【讨论】:

【参考方案9】:

我在更新 blob 数据时遇到了同样的问题——需要将图像更新到数据库中。比我找到一些解决方案如下。更多详情update image into database

LobHandler lobHandler = new DefaultLobHandler(); statusRes = jdbcTemplate.update("更新用户集 FILE_CONTENT = ?, FILE_NAME = ? WHERE lower(USER_ID) = ?", new Object[] new SqlLobValue(image, lobHandler),fileName,userIdLower, 新的 int[] Types.BLOB,Types.VARCHAR,Types.VARCHAR);

【讨论】:

【参考方案10】:

可能是这样的:

public class JdbcActorDao implements ActorDao 
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertActor;

public void setDataSource(DataSource dataSource) 
    this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
    this.insertActor =
            new SimpleJdbcInsert(dataSource)
                    .withTableName("t_actor")
                    .usingGeneratedKeyColumns("id");


public void add(Actor actor) 
    Map<String, Object> parameters = new HashMap<String, Object>(2);
    parameters.put("first_name", actor.getFirstName());
    parameters.put("last_name", actor.getLastName());
    Number newId = insertActor.executeAndReturnKey(parameters);
    actor.setId(newId.longValue());


//  ... additional methods

【讨论】:

actor.getLastName() 不能是 blob 吗?【参考方案11】:

请使用:

addValue("p_file", noDataDmrDTO.getFile_data(), Types.BINARY)

noDataDmrDTO.getFile_data() is byte array.



 simpleJdbcCall =
          new SimpleJdbcCall(jdbcTemplate).withProcedureName("insert_uploaded_files").withCatalogName("wct_mydeq_stg_upld_pkg")
              .withSchemaName("WCT_SCHEMA");

 SqlParameterSource sqlParms =
        new MapSqlParameterSource().addValue("p_upload_idno", Integer.parseInt("143"))
            .addValue("p_file_type_idno", Integer.parseInt(noDataDmrDTO.getFile_type_idno())).addValue("p_file_name", noDataDmrDTO.getFile_name())
            .addValue("p_file", noDataDmrDTO.getFile_data(), Types.BINARY).addValue("p_comments", noDataDmrDTO.getComments())
            .addValue("p_userid", noDataDmrDTO.getUserid());


    simpleJdbcCallResult = simpleJdbcCall.execute(sqlParms);


【讨论】:

介意解释一下你的答案吗?

以上是关于Spring JdbcTemplate - 插入 blob 并返回生成的密钥的主要内容,如果未能解决你的问题,请参考以下文章

Spring JdbcTemplate - 插入 blob 并返回生成的密钥

spring jdbcTemplate insert插入Oracle数据库后返回当前主键id

为啥 Spring 的 jdbcTemplate.batchUpdate() 这么慢?

如何使用 Spring Jdbctemplate.update(String sql, obj...args) 获取插入的 id

Spring JdbcTemplate batchUpdate() 实例

spring JdbcTemplate批量插入 怎么获得数据库自动增长的id