Spring存储过程通过ARRAY

时间:2015-10-30 01:24:38

标签: java spring oracle stored-procedures plsql

我正在从Spring调用存储过程,但我需要将一个字符串数组传递给存储过程。但到目前为止,我遇到错误后的错误。事情出错的任何想法?

我在Oracle中创建了一个自定义类型。

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

我定义了以下存储过程。但是目前我只是让它迭代一个类型为TABLE OF的参数,以确保数据实际上是这样做的。但是我现在甚至无法从Java调用该过程。

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;

我正在扩展Spring的StoredProcedure类,如下所示:

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

我正在尝试运行以下测试。

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

这是我得到的堆栈跟踪。

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

2 个答案:

答案 0 :(得分:2)

由于Oracle中没有“内置”数组类型(至少直接对应于Java的数组类型),因此需要进行一些操作才能成功将Java数组作为参数传递给存储过程。这是怎么回事:

  1. 定义一个可重用的Spring AbstractSqlTypeValue,它将表示您的字符串数组类型:
  2. 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);
        }
    }
    

    此类型的实例需要访问a)本机('unwrapped')oracle连接(而不是代理)和b)传入的values数组。

    1. execute方法中,您需要通过Connection获取原生JDBCTemplate对象,并且确保每次都关闭代理连接。我们发现虽然通常 Spring JDBCTemplate将负责确保每个连接都已正确关闭并返回到池中,但当解开连接时,不会发生因此有必要自己做,在这种情况下使用try-with-resources块,例如:
    2. 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..
          }
      }
      

      这有点单调乏味,可能有更好的方法可以转换为自定义Oracle数组类型 - 但这种技术对我们来说效果很好。

答案 1 :(得分:0)

除了您正在获得它之外

  

java.sql.SQLException:无法转换为内部表示:

数据类型不匹配。您可能正在尝试将String值分配给数据库中的字段,该字段可能不是字符串,也不是整数或其他内容。

我的建议是在您的数据库中执行查询,其次查看字段定义并检查您是否尝试插入不同数据类型的值。让我知道它是如何欢呼!!