JdbcTemplate - 使用SQL MERGE

时间:2017-04-11 15:18:32

标签: spring oracle blob jdbctemplate sql-merge

使用JdbcTemplate我想调用MERGE SQL语句,该语句将向表中插入新记录,或者如果已存在具有特定键的行,则更新。关键部分是其中一列是Oracle BLOB类型。

这是我到现在为止所做的:

尝试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. 如果我将进行第二次尝试,但只将BLOB输入参数放入一次(例如仅在MERGE语句的INSERT部分中),它可以正常工作。但当然这并不能解决我的问题。

  3. 你能帮忙吗?

2 个答案:

答案 0 :(得分:4)

由于@gvenzi的答案,我解决了这个问题,但我决定发布自己的答案,因为我还有一些额外的评论。

所以,是的,OracleLobHandler解决了这个问题。但实际上我们并没有被迫使用弃用的类。在OracleLobHandler documentation我找到了

  

已过时。支持Oracle 10g驱动程序的DefaultLobHandler   更高。考虑使用10g / 11g驱动程序甚至对Oracle   9i数据库! DefaultLobHandler.setCreateTemporaryLob(boolean)是   直接相当于这个OracleLobHandler的实现策略,   只使用标准的JDBC 4.0 API。也就是说,在大多数情况下,定期   DefaultLobHandler设置也可以正常工作。

我测试了它并且有效。

但我在SqlLobValue中使用OracleTypes.BLOBPreparedStatementSetter时遇到了另一个问题(此处为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 :(得分:1)

我不是真正的Spring框架专家,但我可以重现并稍微调试一下你的问题。它与您传递的add_experimental_option prefs ${prefs}有关,它似乎被DefaultLobHandler数据类型绑定而不是LONG错误。

以下是上述调用的简化测试用例,批量大小为1:

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

现在我将LOB处理程序从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

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。希望这有帮助!