Oracle Autoincrement功能:11.2中的触发器或Oracle JDBC CallableStatement?

时间:2015-08-18 16:34:51

标签: java oracle jdbc oracle11g database-performance

当您需要使用JDBC检索新生成的密钥时,在Oracle(11.2)中实现自动增量功能的最佳方法(在插入性能方面)?

我知道Oracle 12中有标识列,但我现在仍然坚持使用11.2。

和其他许多人一样,我没有把JDBC getGeneratedKeys()与Oracle一起工作。我最终在我的Oracle(11.2)数据库中触发了一个类似于MySQL自动增量函数的触发器,并且只要有一个插入到该表中,就从表特定序列中插入NextVal作为其主键。这使得新插入的密钥变得困难,我最终做了第二次查询以获得新生成的密钥。

最近我发现了带有返回值的CallableStatement,我看到如何通过1次调用来完成所有操作。

当你还需要新生成的密钥时,这种最后一种方法通常是一种更快速的插入方法吗?或者我有一个更好的选择,我错过了吗?

2 个答案:

答案 0 :(得分:1)

我在迷你基准测试中得到了积极的结果并决定分享它。

测试代码:

import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.util.Assert;
import org.springframework.util.StopWatch;

import java.sql.*;

public class TriggerPerformanceTest {
    private static final int STEPS_COUNT = 1000;

    public static void main(String[] args) throws SQLException {
        final Connection connection = DriverManager.getConnection("jdbc:oracle:thin:@host:1521/oradev", "user", "pass");

        prepare(connection);

        final StopWatch stopWatch = new StopWatch("mini-bench");

        testTrigger(connection, stopWatch);
        testSequence(connection, stopWatch);
        testSeparateCalls(connection, stopWatch);

        JdbcUtils.closeConnection(connection);

        System.out.println(stopWatch.prettyPrint());
    }

    private static void testTrigger(Connection connection, StopWatch stopWatch) throws SQLException {
        final PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test_table_trigger (text) VALUES (?)", new String[]{"ID"});
        stopWatch.start("with trigger");
        for (int i = 0; i < STEPS_COUNT; i++) {
            preparedStatement.setString(1, "test");
            preparedStatement.executeUpdate();

            final ResultSet resultSet = preparedStatement.getGeneratedKeys();
            final boolean next = resultSet.next();
            Assert.state(next, "Expected not empty result set with generated keys");
            final long id = resultSet.getLong(1);
            Assert.state(id > 0, "Expected generated key value");
            JdbcUtils.closeResultSet(resultSet);
        }
        stopWatch.stop();
        JdbcUtils.closeStatement(preparedStatement);
    }

    private static void testSequence(Connection connection, StopWatch stopWatch) throws SQLException {
        final PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test_table_sequence (id, text) VALUES (sq_test2.NEXTVAL, ?)", new String[]{"ID"});
        stopWatch.start("without trigger");
        for (int i = 0; i < STEPS_COUNT; i++) {
            preparedStatement.setString(1, "test");
            preparedStatement.executeUpdate();

            final ResultSet resultSet = preparedStatement.getGeneratedKeys();
            final boolean next = resultSet.next();
            Assert.state(next, "Expected not empty result set with generated keys");
            final long id = resultSet.getLong(1);
            Assert.state(id > 0, "Expected generated key value");
            JdbcUtils.closeResultSet(resultSet);
        }
        stopWatch.stop();
        JdbcUtils.closeStatement(preparedStatement);
    }

    private static void testSeparateCalls(Connection connection, StopWatch stopWatch) throws SQLException {
        final PreparedStatement preparedStatementSeq = connection.prepareStatement("SELECT sq_test3.NEXTVAL FROM dual");
        final PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO test_table_generated (id, text) VALUES (?, ?)");

        stopWatch.start("separate calls");
        for (int i = 0; i < STEPS_COUNT; i++) {
            final ResultSet resultSet = preparedStatementSeq.executeQuery();
            resultSet.next();
            final long id = resultSet.getLong(1);
            JdbcUtils.closeResultSet(resultSet);
            preparedStatement.setLong(1, id);
            preparedStatement.setString(2, "test");
            preparedStatement.executeUpdate();
        }
        stopWatch.stop();
        JdbcUtils.closeStatement(preparedStatementSeq);
        JdbcUtils.closeStatement(preparedStatement);
    }

    private static void prepare(Connection connection) throws SQLException {
        Statement statement = connection.createStatement();
        try {
            statement.execute("DROP TABLE test_table_sequence");
            statement.execute("DROP TABLE test_table_trigger");
            statement.execute("DROP TABLE test_table_generated");
            statement.execute("DROP SEQUENCE sq_test1");
            statement.execute("DROP SEQUENCE sq_test2");
            statement.execute("DROP SEQUENCE sq_test3");
        } catch (SQLException sqle) {
            //ignore
        }

        try {
            statement.execute("CREATE TABLE test_table_sequence (id NUMBER, text VARCHAR2(10))");
            statement.execute("CREATE TABLE test_table_trigger (id NUMBER, text VARCHAR2(10))");
            statement.execute("CREATE TABLE test_table_generated (id NUMBER, text VARCHAR2(10))");
            statement.execute("CREATE SEQUENCE sq_test1 START WITH 1 INCREMENT BY 1 CACHE 20");
            statement.execute("CREATE SEQUENCE sq_test2 START WITH 1 INCREMENT BY 1 CACHE 20");
            statement.execute("CREATE SEQUENCE sq_test3 START WITH 1 INCREMENT BY 1 CACHE 20");
            statement.execute("CREATE OR REPLACE TRIGGER trg_increment BEFORE INSERT ON test_table_trigger FOR EACH ROW\n" +
                              "BEGIN\n" +
                              "  SELECT sq_test1.NEXTVAL INTO :new.id FROM dual;\n" +
                              "END;");
        } catch (SQLException sqle) {
            sqle.printStackTrace();
        }

        try {
            statement.execute("TRUNCATE TABLE test_table_sequence");
            statement.execute("TRUNCATE TABLE test_table_trigger");
            statement.execute("TRUNCATE TABLE test_table_generated");
        } catch (SQLException sqle) {
            sqle.printStackTrace();
        }
    }
}

输出:

StopWatch 'mini-bench': running time (millis) = 27430
-----------------------------------------
ms     %     Task name
-----------------------------------------
09214  034%  with trigger
08916  033%  without trigger
09300  034%  separate calls

结论:差异很小......要考虑到这一点。

PS。专用的Oracle 11.2.0.4,LAN 1Gb / s,Java 1.7.0_65。

答案 1 :(得分:0)

  

和其他许多人一样,我没有运气让JDBC getGeneratedKeys()与Oracle一起工作

实际上这很容易。

以下适用于我的触发器和Oracle 11.2和驱动程序版本11.2.0.3.0(以及11.2.0.43.0)

create sequence foo_seq;
create table foo (id integer not null primary key, some_data varchar(20));

触发器:

create trigger foo_trg 
  before insert on foo
  for each row
begin
  :new.id := foo_seq.nextval;
end;
/

Java代码:

String insert = "insert into foo (some_data) values (?)"; 
PreparedStatement pstmt = conection.prepareStatement(insert, new String[]{"ID"});

pstmt.setString(1, "bla");
pstmt.executeUpdate();

ResultSet rs = pstmt.getGeneratedKeys();
while (rs.next()) {
  long id = rs.getLong(1);
  System.out.println("The generated ID was: " + id);
}
rs.close();

如果由于性能考虑而不想要触发器,如果​​您更改insert语句以使用序列,则上述代码无需触发器即可运行:

String insert = "insert into foo (id, some_data) values (foo_seq.nextval, ?)";