2017-04-11 179 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