简体   繁体   中英

Spring Stored Procedure Pass ARRAY

I'm working on calling a stored procedure from Spring but I need to pass an array of strings to the stored procedure. However thus far I have encountered error after error. Any ideas where things are going wrong?

I've created a custom type in Oracle.

create or replace type type_string_array 
AS TABLE OF VARCHAR(255);

I have the following stored procedure defined. However for the moment I'm just having it iterate through one of the parameters that is an type TABLE OF to ensure the data is actually making it. However I can't even call the procedure from Java at this point.

procedure sp_save_publication(
    p_id out t_tr_publication.id%type,
    p_tr_uid in t_tr_publication.tr_uid%type,
    p_title in t_tr_publication.title%type,
    p_item_title in t_tr_publication.item_title%type,
    p_cover_date in t_tr_publication.cover_date%type,
    p_issue in t_tr_publication.issue%type,
    p_sort_date in t_tr_publication.sort_date%type,
    p_volume in t_tr_publication.volume%type,
    p_page_begin in t_tr_publication.page_begin%type,
    p_page_end in t_tr_publication.page_end%type,
    p_accession_no in t_tr_publication.accession_no%type,
    p_issn in t_tr_publication.issn%type,
    p_doi in t_tr_publication.doi%type,
    p_doctypes in type_string_array,
    p_headings in type_string_array,
    p_keywords in type_string_array,
    p_organizations in type_string_array,
    p_publishers in type_string_array,
    p_sub_headings in type_string_array,
    p_sub_organizations in type_string_array,
    p_subjects in type_string_array
  ) AS
  BEGIN

    if p_doctypes.count > 0
    then

      for i in p_doctypes.FIRST .. p_doctypes.LAST
      loop

        pkg_logger.sp_log_error(
          p_code => null,
          p_message => p_doctypes(i),
          p_package_name => package_name,
          p_procedure_name => 'sp_save_publication'
        );

      end loop;

    end if;  

  END sp_save_publication;

I am extending Spring's StoredProcedure class as follows:

public class SPSavePublication extends StoredProcedure {

    public SPSavePublication(JdbcTemplate jdbcTemplate) {

        super(jdbcTemplate,"PKG_THOMSON_REUTER.sp_save_publication");
        declareParameter(new SqlOutParameter("p_id", Types.INTEGER));
        declareParameter(new SqlParameter("p_tr_uid",Types.VARCHAR));
        declareParameter(new SqlParameter("p_title",Types.VARCHAR));
        declareParameter(new SqlParameter("p_item_title",Types.VARCHAR));
        declareParameter(new SqlParameter("p_cover_date",Types.VARCHAR));
        declareParameter(new SqlParameter("p_issue", Types.VARCHAR));
        declareParameter(new SqlParameter("p_sort_date",Types.DATE));
        declareParameter(new SqlParameter("p_volume",Types.VARCHAR));
        declareParameter(new SqlParameter("p_page_begin",Types.VARCHAR));
        declareParameter(new SqlParameter("p_page_end",Types.VARCHAR));
        declareParameter(new SqlParameter("p_accession_no",Types.VARCHAR));
        declareParameter(new SqlParameter("p_issn",Types.VARCHAR));
        declareParameter(new SqlParameter("p_doi",Types.VARCHAR));
        declareParameter(new SqlParameter("p_doctypes",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_headings",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_keywords",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_organizations",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_publishers",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_sub_headings",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_sub_organizations",Types.ARRAY,"TYPE_STRING_ARRAY"));
        declareParameter(new SqlParameter("p_subjects",Types.ARRAY,"TYPE_STRING_ARRAY"));
        setFunction(false);
        compile();
    }

    public long execute(String uid, String title, String itemTitle, String coverDate,
                        String issue, Date sortDate, String volume, String pageBegin,
                        String pageEnd, String accessionNo, String issn, String doi,
                        String[] doctypes, String[] headings, String[] keywords,
                        String[] organizations, String[] publishers, String[] subHeadings,
                        String[] subOrganizations, String[] subjects) {

        Map results = super.execute(uid,title,itemTitle,coverDate,issue,sortDate,volume,
                pageBegin,pageEnd,accessionNo,issn,doi,doctypes,headings,keywords,organizations,
                publishers,subHeadings,subOrganizations,subjects);
        return (int) results.get("p_id");
    }
}

I have the following Test I am trying to run.

@Test
    public void testSPSavePublication() {

        String[] docTypesArray = {"journal","doctypes"};

        SPSavePublication spSavePublication = new SPSavePublication(jdbcTemplate);
        spSavePublication.execute(null,null,null,null,null,new Date(),null,null,null,null,
                null,null,docTypesArray,docTypesArray,docTypesArray,docTypesArray,docTypesArray,
                docTypesArray,docTypesArray,docTypesArray);
    }

This is the stacktrace I'm getting.

org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException for SQL [{call PKG_THOMSON_REUTER.sp_save_publication(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}]; SQL state [99999]; error code [17059]; Fail to convert to internal representation: [Ljava.lang.String;@7b10472e; nested exception is java.sql.SQLException: Fail to convert to internal representation: [Ljava.lang.String;@7b10472e
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:84)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:1137)
    at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1173)
    at org.springframework.jdbc.object.StoredProcedure.execute(StoredProcedure.java:123)
    at org.company.app.procedures.thomsonreuter.SPSavePublication.execute(SPSavePublication.java:53)
    at org.company.app.ThomsonReutersTests.testSPSavePublication(ThomsonReutersTests.java:41)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:73)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:224)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:83)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:68)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:163)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
Caused by: java.sql.SQLException: Fail to convert to internal representation: [Ljava.lang.String;@7b10472e
    at oracle.sql.ARRAY.toARRAY(ARRAY.java:244)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:10472)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:9966)
    at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:10590)
    at oracle.jdbc.driver.OracleCallableStatement.setObject(OracleCallableStatement.java:6119)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:249)
    at com.mchange.v2.c3p0.impl.NewProxyCallableStatement.setObject(NewProxyCallableStatement.java:4025)
    at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:432)
    at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:235)
    at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:150)
    at org.springframework.jdbc.core.CallableStatementCreatorFactory$CallableStatementCreatorImpl.createCallableStatement(CallableStatementCreatorFactory.java:213)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:1115)
    ... 36 more

As there is no 'built-in' array type in Oracle (that directly corresponds to Java's array type at least), there is a few more hoops to jump through to successfully pass Java arrays as parameters to stored procedures. Here's how it goes:

  1. Define a reusable Spring AbstractSqlTypeValue that will represent your array-of-strings type:
class OracleArrayType extends AbstractSqlTypeValue {

    private final Connection oracleCon;
    private final Object[] values;

    public OracleArrayType(final Connection oracleCon, final Object[] values) {
        this.oracleCon = oracleCon;
        this.values = values;
    }

    @Override
    protected Object createTypeValue(final Connection con, final int sqlType, final String typeName)
            throws SQLException {
        return new ARRAY(ArrayDescriptor.createDescriptor(typeName, oracleCon), oracleCon, values);
    }
}

Instances of this type will need access to a) the native ('unwrapped') oracle connection (rather than the proxy) and b) the values array being passed in.

  1. Within your execute method you need to obtain the native Connection object via JDBCTemplate , and crucially ensure that the proxy connection is closed each time. We found that whilst normally Spring's JDBCTemplate would be responsible for ensuring each connection is properly closed and returned to the pool, when the connection is unwrapped this does not happen and its therefore necessary to do it yourself, in this case using a try-with-resources block, eg:
public int execute(String uid, String title, String itemTitle, String coverDate, String issue, Date sortDate,
        String volume, String pageBegin, String pageEnd, String accessionNo, String issn, String doi,
        String[] doctypes, String[] headings, String[] keywords, String[] organizations, String[] publishers,
        String[] subHeadings, String[] subOrganizations, String[] subjects) {

    // Ensure proxy connection is closed properly
    try (final Connection connectionWrapper = getJdbcTemplate().getDataSource().getConnection()) {

        // Obtain native connection
        final Connection oracleConnection =
                getJdbcTemplate().getNativeJdbcExtractor().getNativeConnection(connectionWrapper);

        AbstractSqlTypeValue docTypeArray = new OracleArrayType(oracleConnection, doctypes);
        AbstractSqlTypeValue headingsArray = new OracleArrayType(oracleConnection, headings);
        AbstractSqlTypeValue keywordsArray = new OracleArrayType(oracleConnection, keywords);
        AbstractSqlTypeValue organizationsArray = new OracleArrayType(oracleConnection, organizations);
        AbstractSqlTypeValue publishersArray = new OracleArrayType(oracleConnection, publishers);
        AbstractSqlTypeValue subHeadingsArray = new OracleArrayType(oracleConnection, subHeadings);
        AbstractSqlTypeValue subOrganizationsArray = new OracleArrayType(oracleConnection, subOrganizations);
        AbstractSqlTypeValue subjectsArray = new OracleArrayType(oracleConnection, subjects);

        Map results = super.execute(uid, title, itemTitle, coverDate, issue, sortDate, volume,
                pageBegin, pageEnd, accessionNo, issn, doi, docTypeArray, headingsArray, keywordsArray,
                organizationsArray, publishersArray, subHeadingsArray, subOrganizationsArray, subjectsArray);

        return (int) results.get("p_id");
    } catch (SQLException e) {
        throw new DataRetrievalFailureException(e.getMessage()); // or whatever..
    }
}

It's a bit tedious and there may be better ways to translate into the custom Oracle array type - but this technique worked well for us.

From the exception that your are getting it seems that

java.sql.SQLException: Fail to convert to internal representation:

there is a datatype mismatch . you may be trying to assign a String value to a field in database which may be defined not a String and as a Integer or something.

my suggestion would be to execute the query in you data base, secondly see the field definition and check that you are trying to insert a value of different data type. let me know how it goes cheers!!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM