在Oracle上进行INSERT...SELECT操作后如何获取插入的ID?

10

如果我从我的Oracle客户端(SQL Developer)运行此SQL语句,则可以正常工作:

insert into Person (Name) select 'Bob' from dual

如果我通过Spring JDBC发出它而不使用KeyHolder,它也可以正常工作:

final PreparedStatementCreator psc = new PreparedStatementCreator() {

    @Override
    public PreparedStatement createPreparedStatement(Connection con)
        throws SQLException
    {
        return con.prepareStatement(
                "insert into Person (Name) select 'Bob' from dual");
    }
};
jdbcOperations.update(psc);

然而,我需要使用KeyHolder来获取新插入行的ID。如果我修改上面的代码以使用KeyHolder,则如下所示:

final KeyHolder keyHolder = new GeneratedKeyHolder();
final PreparedStatementCreator psc = new PreparedStatementCreator() {

    @Override
    public PreparedStatement createPreparedStatement(Connection con)
        throws SQLException
    {
        return con.prepareStatement(
            "insert into Person (Name) select 'Bob' from dual",
            new String[] {"PersonID"});
    }
};
jdbcOperations.update(psc, keyHolder);

...然后我遇到了这个错误:

Exception in thread "main" org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: ORA-00933: SQL command not properly ended
    at org.springframework.jdbc.support.SQLExceptionSubclassTranslator.doTranslate(SQLExceptionSubclassTranslator.java:94)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:602)
    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:842)
    at au.com.bisinfo.codecombo.logic.ImportServiceImpl.insertLoginRedirectRule(ImportServiceImpl.java:107)
    at au.com.bisinfo.codecombo.logic.ImportServiceImpl.runImport(ImportServiceImpl.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy8.runImport(Unknown Source)
    at au.com.bisinfo.codecombo.ui.Main.main(Main.java:39)
Caused by: java.sql.SQLSyntaxErrorException: ORA-00933: SQL command not properly ended
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439)
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395)
    at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802)
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436)
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186)
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521)
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205)
    at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1008)
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1307)
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449)
    at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3530)
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1350)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:105)
    at org.springframework.jdbc.core.JdbcTemplate$3.doInPreparedStatement(JdbcTemplate.java:844)
    at org.springframework.jdbc.core.JdbcTemplate$3.doInPreparedStatement(JdbcTemplate.java:1)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:586)
    ... 15 more

FWIW,如果我使用INSERT ... VALUES而不是INSERT ... SELECT,一切都很好(尽管这对我没有帮助,因为我需要选择东西):

final KeyHolder keyHolder = new GeneratedKeyHolder();
final PreparedStatementCreator psc = new PreparedStatementCreator() {

    @Override
    public PreparedStatement createPreparedStatement(Connection con)
        throws SQLException
    {
        return con.prepareStatement(
            "insert into Person (Name) values ('Bob')",
            new String[] {"PersonID"});
    }
};
jdbcOperations.update(psc, keyHolder);

我使用的是:
  • Spring JDBC 3.0.3.RELEASE
  • JDBC驱动程序:ojdbc6.jar版本11.2.0.1.0
  • RDBMS:Oracle9i Release 9.2.0.5.0 - Production
  • commons-dbcp 1.4

N.B.我的应用程序需要使用标准SQL以保持数据库的中立性,这排除了任何特定于Oracle的SQL(在实际情况下,我不会从“dual”中进行选择)。

感谢您的帮助。

4个回答

1

java.sql.Connection.prepareStatement(java.lang.String, int) 接口很清晰

创建一个默认的 PreparedStatement 对象,具有检索自动生成键的功能

所以你正在使用错误的方法。尝试使用

return con.prepareStatement(
        "insert into Person (Name) select 'Bob' from dual",
        Statement.RETURN_GENERATED_KEYS);

替代


该方法是可以返回生成的键之一的三个方法,另外两个方法是prepareStatement(String,int [])和prepareStatement(String,String [])。我正在使用后者,这在我的经验中是适用于Oracle的正确方法。不过,我会尝试您的建议并告诉您进展如何。 - Andrew Swan
@Andrew Swan 没错。谢谢你!但让我知道一下输出结果。 - Arthur Ronald

0

怎么样?

INSERT INTO blah b (blah1, blah2, blah3)
VALUES (?, ?, ?) RETURNING b.id INTO ?";

我不能使用INSERT ... VALUES,因为我需要选择内容。此外,我不想使用RETURNING .. INTO,因为据我所知它是Oracle特定的。 - Andrew Swan

0

Oracle JDBC驱动程序不支持此功能


1
这是我猜测的,你有相关的参考资料吗? - Andrew Swan

0

我怀疑在INSERT SELECT语句中使用KeyHolder不会被支持,因为SELECT理论上可以选择多行,如果这样做,就没有办法将这些多个键返回到单个KeyHolder中。对于您想要实现的目标,使用SELECT语句后跟一个INSERT语句可能会更容易。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接