2017-04-11 173 views
3

使用JdbcTemplate我想調用MERGE SQL語句,該語句將向表中插入新記錄,或者在具有特定鍵的行已存在時更新。關鍵部分是其中一列是Oracle BLOB類型。JdbcTemplate - 使用SQL插入或更新Oracle BLOB MERGE

這裏是我試過至今:

嘗試1.

SQL語句:

String sql = "" 
      + "MERGE INTO file_thumbnails " 
      + "  USING (SELECT ? as file_c_id, ? as thumbnail_type, ? as thumbnail_image FROM DUAL) tmp " 
      + "  ON (file_thumbnails.file_c_id = tmp.file_c_id AND " 
      + "   file_thumbnails.thumbnail_type = tmp.thumbnail_type) " 
      + "  WHEN MATCHED THEN " 
      + "  UPDATE " 
      + "   SET thumbnail_image = tmp.thumbnail_image " 
      + "    ,thumbnail_date = SYSDATE " 
      + "  WHEN NOT MATCHED THEN " 
      + "  INSERT (c_id, file_c_id, thumbnail_type, thumbnail_image, thumbnail_date) " 
      + "  VALUES (cedar_c_id_seq.nextval, tmp.file_c_id, tmp.thumbnail_type, tmp.thumbnail_image, SYSDATE)"; 

數據庫調用:

List<Object[]> x = fileList.stream().map(file -> { 
    byte[] thumbnail = file.getThumbnail(); 
    SqlLobValue sqlLobValue = new SqlLobValue(new ByteArrayInputStream(thumbnail), thumbnail.length, new DefaultLobHandler()); 
    return new Object[] { file.getFileCId(), file.getType().toString(), sqlLobValue}; 
}).collect(Collectors.toList()); 
jdbcTemplate.batchUpdate(sql, x, new int[] { OracleTypes.NUMBER, OracleTypes.VARCHAR, OracleTypes.BLOB}); 

例外:

Caused by: org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [MERGE INTO file_thumbnails  USING (SELECT ? as file_c_id, ? as thumbnail_type, ? as thumbnail_image FROM DUAL) tmp   ON (file_thumbnails.file_c_id = tmp.file_c_id AND    file_thumbnails.thumbnail_type = tmp.thumbnail_type)  WHEN MATCHED THEN   UPDATE   SET thumbnail_image = tmp.thumbnail_image    ,thumbnail_date = SYSDATE  WHEN NOT MATCHED THEN   INSERT (c_id, file_c_id, thumbnail_type, thumbnail_image, thumbnail_date)   VALUES (cedar_c_id_seq.nextval, tmp.file_c_id, tmp.thumbnail_type, tmp.thumbnail_image, SYSDATE)]; SQL state [72000]; error code [1461]; ORA-01461: can bind a LONG value only for insert into a LONG column 
; nested exception is java.sql.BatchUpdateException: ORA-01461: can bind a LONG value only for insert into a LONG column 

    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:662) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:950) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.BatchUpdateUtils.executeBatchUpdate(BatchUpdateUtils.java:32) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:1000) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at cern.edms.thumbnails.generator.repository.EdmsFileRepository.saveThumbnails(EdmsFileRepository.java:61) ~[classes/:na] 
    at cern.edms.thumbnails.generator.repository.EdmsFileRepository$$FastClassBySpringCGLIB$$e3d79386.invoke(<generated>) ~[classes/:na] 
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at cern.edms.thumbnails.generator.repository.EdmsFileRepository$$EnhancerBySpringCGLIB$$70f43ba5.saveThumbnails(<generated>) ~[classes/:na] 
    at cern.edms.thumbnails.generator.Application.run(Application.java:58) [classes/:na] 
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE] 
    ... 6 common frames omitted 
Caused by: java.sql.BatchUpdateException: ORA-01461: can bind a LONG value only for insert into a LONG column 

    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10401) ~[ojdbc6-11.2.0.3.0.jar:11.2.0.3.0] 
    at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:230) ~[ojdbc6-11.2.0.3.0.jar:11.2.0.3.0] 
    at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:966) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:950) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    ... 21 common frames omitted 

嘗試2.

Sql statement: 
     String sql = "" 
       + "MERGE INTO file_thumbnails " 
       + "  USING (SELECT ? as file_c_id, ? as thumbnail_type FROM DUAL) tmp " 
       + "  ON (file_thumbnails.file_c_id = tmp.file_c_id AND " 
       + "   file_thumbnails.thumbnail_type = tmp.thumbnail_type) " 
       + "  WHEN MATCHED THEN " 
       + "  UPDATE " 
       + "   SET thumbnail_image = ? " 
       + "    ,thumbnail_date = SYSDATE " 
       + "  WHEN NOT MATCHED THEN " 
       + "  INSERT (c_id, file_c_id, thumbnail_type, thumbnail_image, thumbnail_date) " 
       + "  VALUES (cedar_c_id_seq.nextval, tmp.file_c_id, tmp.thumbnail_type, ?, SYSDATE)"; 

數據庫調用:

List<Object[]> x = fileList.stream().map(file -> { 
    byte[] thumbnail = file.getThumbnail(); 
    SqlLobValue sqlLobValue = new SqlLobValue(new ByteArrayInputStream(thumbnail), thumbnail.length, new DefaultLobHandler()); 
    SqlLobValue sqlLobValue2 = new SqlLobValue(new ByteArrayInputStream(thumbnail), thumbnail.length, new DefaultLobHandler()); 
    return new Object[] { file.getFileCId(), file.getType().toString(), sqlLobValue, sqlLobValue2 }; 
}).collect(Collectors.toList()); 
jdbcTemplate.batchUpdate(sql, x, new int[] { OracleTypes.NUMBER, OracleTypes.VARCHAR, OracleTypes.BLOB, OracleTypes.BLOB }); 

例外:

Caused by: org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [MERGE INTO file_thumbnails  USING (SELECT ? as file_c_id, ? as thumbnail_type FROM DUAL) tmp   ON (file_thumbnails.file_c_id = tmp.file_c_id AND    file_thumbnails.thumbnail_type = tmp.thumbnail_type)  WHEN MATCHED THEN   UPDATE   SET thumbnail_image = ?    ,thumbnail_date = SYSDATE  WHEN NOT MATCHED THEN   INSERT (c_id, file_c_id, thumbnail_type, thumbnail_image, thumbnail_date)   VALUES (cedar_c_id_seq.nextval, tmp.file_c_id, tmp.thumbnail_type, ?, SYSDATE)]; SQL state [63000]; error code [3106]; ORA-03106: fatal two-task communication protocol error 
; nested exception is java.sql.BatchUpdateException: ORA-03106: fatal two-task communication protocol error 

    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:649) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:662) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:950) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.BatchUpdateUtils.executeBatchUpdate(BatchUpdateUtils.java:32) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.batchUpdate(JdbcTemplate.java:1000) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at cern.edms.thumbnails.generator.repository.EdmsFileRepository.saveThumbnails(EdmsFileRepository.java:62) ~[classes/:na] 
    at cern.edms.thumbnails.generator.repository.EdmsFileRepository$$FastClassBySpringCGLIB$$e3d79386.invoke(<generated>) ~[classes/:na] 
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:721) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ~[spring-tx-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:656) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at cern.edms.thumbnails.generator.repository.EdmsFileRepository$$EnhancerBySpringCGLIB$$587b6598.saveThumbnails(<generated>) ~[classes/:na] 
    at cern.edms.thumbnails.generator.Application.run(Application.java:58) [classes/:na] 
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:776) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE] 
    ... 6 common frames omitted 
Caused by: java.sql.BatchUpdateException: ORA-03106: fatal two-task communication protocol error 

    at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10401) ~[ojdbc6-11.2.0.3.0.jar:11.2.0.3.0] 
    at oracle.jdbc.driver.OracleStatementWrapper.executeBatch(OracleStatementWrapper.java:230) ~[ojdbc6-11.2.0.3.0.jar:11.2.0.3.0] 
    at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:966) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate$4.doInPreparedStatement(JdbcTemplate.java:950) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:633) ~[spring-jdbc-4.3.7.RELEASE.jar:4.3.7.RELEASE] 
    ... 21 common frames omitted 

其他注意事項。

  1. 在第二次嘗試在數據庫中調用,因爲我得到一個例外,我不能使用相同的SqlLobValue對象兩次:java.sql.SQLException中::重複流參數:4

  2. 所致

  3. 如果我將第二次嘗試,但將BLOB輸入參數只有一次(例如只在MERGE語句的INSERT部分)它將正常工作。但當然這並不能解決我的問題。

你能幫忙嗎?

回答

3

由於@gvenzi的回答,我解決了這個問題,但決定發佈我自己的答案,因爲我有一些額外的意見。

所以,是的,OracleLobHandler解決了這個問題。但實際上我們並不是被迫使用棄用的類。在OracleLobHandler documentation我發現

Deprecated. in favor of DefaultLobHandler for the Oracle 10g driver and higher. Consider using the 10g/11g driver even against an Oracle 9i database! DefaultLobHandler.setCreateTemporaryLob(boolean) is the direct equivalent of this OracleLobHandler's implementation strategy, just using standard JDBC 4.0 API. That said, in most cases, regular DefaultLobHandler setup will work fine as well.

我測試了它,它的工作原理。

但我OracleTypes.BLOBPreparedStatementSetter共進使用SqlLobValue另一個問題(這是這裏所描述ClassCastException: SqlLobValue cannot be cast to oracle.sql.BLOB using PreparedStatementSetter

我最後的工作代碼如下:

public void saveThumbnails(List<Thumbnail> fileList) throws SQLException, IOException { 

    BatchPreparedStatementSetter b = new BatchPreparedStatementSetter() { 
     @Override 
     public void setValues(PreparedStatement ps, int i) throws SQLException { 
      Thumbnail thumbnail = fileList.get(i); 
      byte[] thumbnailBytes = thumbnail.getThumbnail(); 
      ps.setObject(1, thumbnail.getFileCId(), OracleTypes.NUMBER); 
      ps.setObject(2, thumbnail.getType().toString(), OracleTypes.VARCHAR); 
      DefaultLobHandler lobHandler = new DefaultLobHandler(); 
      lobHandler.setCreateTemporaryLob(true); 
      lobHandler.getLobCreator().setBlobAsBytes(ps, 3, thumbnailBytes); 
     } 

     @Override 
     public int getBatchSize() { 
      return fileList.size(); 
     } 
    }; 
    jdbcTemplate.batchUpdate(getSaveThumbnailSql(), b); 
} 

private String getSaveThumbnailSql() { 
    // @formatter:off 
    String sql = "" 
      + "MERGE INTO file_thumbnails " 
      + "  USING (SELECT ? as file_c_id, ? as thumbnail_type, ? AS thumbnail_image FROM DUAL) tmp " 
      + "  ON (file_thumbnails.file_c_id = tmp.file_c_id AND " 
      + "   file_thumbnails.thumbnail_type = tmp.thumbnail_type) " 
      + "  WHEN MATCHED THEN " 
      + "  UPDATE " 
      + "   SET thumbnail_image = tmp.thumbnail_image" 
      + "    ,thumbnail_date = SYSDATE " 
      + "  WHEN NOT MATCHED THEN " 
      + "  INSERT (c_id, file_c_id, thumbnail_type, thumbnail_image, thumbnail_date) " 
      + "  VALUES (cedar_c_id_seq.nextval, tmp.file_c_id, tmp.thumbnail_type, tmp.thumbnail_image , SYSDATE)"; 
    //@formatter:on 
    return sql; 
} 
1

我不是一個真正的Spring框架專家,但我可以重現和有點調試你的問題。它與DefaultLobHandler有關,你通過一個看起來像是LONG數據類型而不是BLOB的錯誤綁定的數據類型。

這是你上面調用的簡化測試用例一個的批量大小:

String sql = "MERGE INTO file_thumbnails " 
     + "  USING (SELECT ? as file_c_id, ? as thumbnail_type, ? as thumbnail_image FROM DUAL) tmp " 
     + "  ON (file_thumbnails.file_c_id = tmp.file_c_id AND " 
     + "   file_thumbnails.thumbnail_type = tmp.thumbnail_type) " 
     + "  WHEN MATCHED THEN " 
     + "  UPDATE " 
     + "   SET thumbnail_image = tmp.thumbnail_image " 
     + "    ,thumbnail_date = SYSDATE " 
     + "  WHEN NOT MATCHED THEN " 
     + "  INSERT (file_c_id, thumbnail_type, thumbnail_image, thumbnail_date) " 
     + "  VALUES (tmp.file_c_id, tmp.thumbnail_type, tmp.thumbnail_image, SYSDATE)"; 

byte[] content = Files.readAllBytes(Paths.get("/Users/gvenzl/Downloads/image1.JPG")); 
ByteArrayInputStream bin = new ByteArrayInputStream(content); 
SqlLobValue sqlLobValue = new SqlLobValue(bin, content.length, new DefaultLobHandler()); 
List<Object []> x = new ArrayList<Object []>(); 
x.add(new Object [] { 1, "Test", sqlLobValue}); 

jdbcTemplate.batchUpdate(sql, x, new int[] { OracleTypes.NUMBER, OracleTypes.VARCHAR, OracleTypes.BLOB}); 

System.out.print("Successful!"); 

我讀的圖像,然後創建一個單一的項目陣列並執行相同的方式,你有和錯誤:

Exception in thread "main" org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [MERGE INTO file_thumbnails  USING (SELECT ? as file_c_id, ? as thumbnail_type, ? as thumbnail_image FROM DUAL) tmp   ON (file_thumbnails.file_c_id = tmp.file_c_id AND    file_thumbnails.thumbnail_type = tmp.thumbnail_type)  WHEN MATCHED THEN   UPDATE   SET thumbnail_image = tmp.thumbnail_image    ,thumbnail_date = SYSDATE  WHEN NOT MATCHED THEN   INSERT (file_c_id, thumbnail_type, thumbnail_image, thumbnail_date)   VALUES (tmp.file_c_id, tmp.thumbnail_type, tmp.thumbnail_image, SYSDATE)]; SQL state [72000]; error code [1461]; ORA-01461: can bind a LONG value only for insert into a LONG column 
; nested exception is java.sql.BatchUpdateException: ORA-01461: can bind a LONG value only for insert into a LONG column 

現在我從DefaultLobHandler改變LOB處理程序爲已過時OracleLobHandler

byte[] content = Files.readAllBytes(Paths.get("/Users/gvenzl/Downloads/image1.JPG")); 
ByteArrayInputStream bin = new ByteArrayInputStream(content); 
SqlLobValue sqlLobValue = new SqlLobValue(bin, content.length, new OracleLobHandler()); 
List<Object []> x = new ArrayList<Object []>(); 
x.add(new Object [] { 1, "Test", sqlLobValue}); 

jdbcTemplate.batchUpdate(sql, x, new int[] { OracleTypes.NUMBER, OracleTypes.VARCHAR, OracleTypes.BLOB}); 

System.out.print("Successful!"); 

而我的是:

Successful! 

調試通過它,我可以看到的區別是,OracleLobHandler使用ps.setBlob()方法而DefaultLobHandler使用ps.setBinaryStream()這似乎導致入變量被綁定爲LONG而不是BLOB。希望這可以幫助!

+0

謝謝!它解決了這個問題,但也請查看我的答案並附加一些評論。 – kpater87