【问题标题】:Spring JdbcTemplate - Insert blob and return generated keySpring JdbcTemplate - 插入 blob 并返回生成的密钥
【发布时间】: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

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

【问题讨论】:

    标签: java jdbctemplate spring-jdbc


    【解决方案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);
                
                }
                

                【讨论】:

                • 介意解释一下你的答案吗?
                猜你喜欢
                • 2017-07-24
                • 2017-10-10
                • 1970-01-01
                • 1970-01-01
                • 2014-12-29
                • 1970-01-01
                • 1970-01-01
                • 2019-08-14
                • 2011-12-28
                相关资源
                最近更新 更多