JdbcTemplate - Insert or Update Oracle BLOB using SQL MERGE

Using JdbcTemplate I would like to invoke a MERGE SQL statement that will insert a new record into the table or update if a row with a specific key already exists. The key part is that one of the columns is an Oracle BLOB.

Here's what I've tried so far:

Try 1.

Sql operator:

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

      

Database call:

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

      

An exception:

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

      

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

      

Database call:

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

      

An exception:

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

      

Additional notes.

  • On the second try in the call to the database, I cannot use the same object twice SqlLobValue

    , because I get an exception:

    Caused by: java.sql.SQLException: Duplicate stream parameter: 4

  • If I go with a second try, but supply only the BLOB input parameter (for example, only in the INSERT part of the MERGE statement), it works correctly. But of course this doesn't solve my problem.

Could you help me?

+3


source to share


2 answers


I solved the problem thanks to @ gvenzi's answer, but decided to post my own answer as I have additional comments.

So, yes, that OracleLobHandler

solves the problem. But we are not really forced to use a deprecated class. In the OracleLobHandler documentation, I found

Obsolete. in favor of DefaultLobHandler for Oracle driver 10g and higher. Consider using the 10g / 11g driver even against Oracle 9i Database! DefaultLobHandler.setCreateTemporaryLob (boolean) is a direct equivalent to this OracleLobHandler implementation strategy, simply using the standard JDBC 4.0 API. However, in most cases, Setting the DefaultLobHandler will also work fine.

I tested it and it works.



But I had another problem using SqlLobValue

together with OracleTypes.BLOB

in PreparedStatementSetter

(here's a ClassCastException: SqlLobValue cannot be passed to oracle.sql.BLOB using PreparedStatementSetter )

My final working code looks like this:

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

      

+4


source


I'm not a Spring framework expert, but I could reproduce and debug your problem a bit. It is linked to the DefaultLobHandler

one you are passing in, which appears to bind as a datatype LONG

and not BLOB

by mistake.

Below is a simplified example of your above call with packet size:

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

      

I read an image and then create a single array of elements and execute it the same way as you do:

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

      



Now I am changing the LOB handler from DefaultLobHandler

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

      

And my output is:

Successful!

      

Debugging through this, the difference I could see was that the OracleLobHandler

method is using ps.setBlob()

while DefaultLobHandler

using ps.setBinaryStream()

, which appears to result in the variable being bound as LONG

, not a BLOB

. Hope this helps!

+1


source







All Articles