2015-08-18 3 views
2

Каков наилучший способ (с точки зрения производительности вставки) реализовать функции автоматического инкремента в Oracle (11.2), когда вам нужно получить вновь сгенерированный ключ с помощью JDBC?Функциональность Oracle Autoincrement: триггеры или Oracle JDBC CallableStatement в 11.2?

Я знаю, что в Oracle 12 есть столбцы с идентификаторами, но сейчас я придерживаюсь 11.2.

Как и многие другие, мне не удавалось получить JDBC getGeneratedKeys() для работы с Oracle. Я закончил с триггером в своей базе данных Oracle (11.2), которая действует как функция автоинкремента MySQL и вставляет NextVal из определенной последовательности таблицы, чтобы действовать как ее первичный ключ всякий раз, когда есть вставка в эту таблицу. Это затрудняло получение недавно вставленного ключа, но в итоге я сделал второй запрос, чтобы получить вновь сгенерированный ключ.

Совсем недавно я обнаружил CallableStatement с возвращаемыми значениями, и я вижу, как они могут быть использованы для выполнения всего 1 вызова.

Это последний способ, как правило, более быстрый способ делать вставки, когда вам также нужен этот вновь сгенерированный ключ, или есть лучший вариант для меня, который мне не хватает?

+1

Не можете ли вы использовать объект (ы) последовательности оракула? –

+1

[Это] (https://hoopercharles.wordpress.com/2011/03/25/sequence-driven-primary-keys-which-is-better-call-nextval-in-the-insert-statement-or-in -a-row-level-trigger /) и [this] (https://www.krenger.ch/blog/oracle-primary-key-sequence-performance/) ссылки могут быть вам полезны. – svaor

+0

Вы можете проверить себя, какой побочный эффект даст JDBC. Я думаю, будет дешевле использовать два запроса. – svaor

ответ

1

Я получил interesing результат в моей мини-тесте и решил поделиться.

Тест Код:

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.

+0

Выглядит хорошо, за исключением того, что не похоже, что testSequence и testTrigger фактически получают автоматически увеличивающийся ключ из последовательности? – ozborn

+0

Что это значит? 'SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name LIKE 'SQ_TEST%'': sq_test1/5001, sq_test2/5001, sq_test3/5001. Все идентификаторы заполнены натуральными числами. – svaor

+0

Я пытаюсь получить производительность, включая получение ключа с автоматическим увеличением. Так что либо testSeparate (который у вас есть), либо команда prepareCall и возвращающий ключ. TestSequence и testTrigger фактически не получают первичный ключ (хотя вы можете это сделать). Имеет ли это смысл? – ozborn

0

Как и многие другие, у меня не было никакой удачи в получении getGeneratedKeys JDBC() для работы с 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(); 

Если вы не хотите запускать из-за соображений производительности, приведенный выше код работает без триггера, если изменить оператор вставки использовать последовательность:

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

Я пробовал это, но я думаю, что я закончил с операцией, не поддерживаемой ошибкой, если я правильно помню. Вот почему я закончил с помощью подхода, который можно вызывать, описанного здесь http://stackoverflow.com/questions/3552260/plsql-jdbc-how-to-get-last-row-id/3552353#3552353 – ozborn

+1

@ozborn: тогда вы вероятно, используя устаревшую версию драйвера _completely_. Драйвер 11.2.0.4 определенно поддержит это. –

Смежные вопросы