2013-04-06 4 views
0

Сценарий: Я столкнулся с некоторым кодом, который смешивает JPA с JDBC в транзакции. JDBC делает INSERT в таблицу с в основном пустой строкой, устанавливая основной ключ на (SELECT MAX(PK) + 1) и middleName на временную метку темпа. Затем метод выбирает из той же таблицы для max(PK) + эту временную метку времени, чтобы проверить, произошло ли столкновение. В случае успеха он отменяет middleName и обновляет. Метод возвращает вновь созданный первичный ключ.JPA: Как установить INSERT PK в MAX (PK) + 1

Вопрос: Есть ли лучший способ вставить объект в базу данных, установив ПК в max(pk) + 1 и получить доступ к этому вновь созданным ПК (желательно с использованием JPA)?

Окружающая среда: Использование EclipseLink и поддержка нескольких версий баз данных Oracle и MS SqlServer.

Bonus фон: Причина, почему я задаю этот вопрос, потому что я бег в java.sql.BatchUpdateException при вызове этого метода, как часть цепи при выполнении тестов интеграции. В верхней части цепи используется JPA EntityManager, чтобы сохранить некоторые объекты.

метод в вопросе

@Override 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
public int generateStudentIdKey() { 
    final long now = System.currentTimeMillis(); 
    int id = 0; 

    try { 

     try (final Connection connection = dataSource.getConnection()) { 

      if (connection.getAutoCommit()) { 
       connection.setAutoCommit(false); 
      } 

      try (final Statement statement = connection.createStatement()) { 
       // insert a row into the generator table 
       statement.executeUpdate(
        "insert into student_demo (student_id, middle_name) " + 
        "select (max(student_id) + 1) as student_id, '" + now + 
         "' as middle_name from student_demo"); 
       try (final ResultSet rs = statement.executeQuery(
        "select max(student_id) as student_id " + 
        "from student_demo where middle_name = '" + now + "'")) { 

         if (rs.next()) { 
          id = rs.getInt(1); 
         } 
       } 

       if (id == 0) { 
        connection.rollback(); 
        throw new RuntimeException("Key was not generated"); 
       } 

       statement.execute("update student_demo set middle_name = null " + 
            "where student_id = " + id); 

      } catch (SQLException statementException) { 
       connection.rollback(); 
       throw statementException; 
      } 
     } 
    } catch (SQLException exception) { 
     throw new RuntimeException(
      "Exception thrown while trying to generate new student_ID", exception); 
    } 

    return id; 
} 
+5

Не делайте этого. Это ужасная идея.Идентификатор не работает должным образом в многопользовательской среде и не масштабируется. Вместо этого используйте последовательность (или любой другой «генератор», поддерживаемый вашей СУБД). JPA может быть настроен на использование средств базы данных для генерации уникальных идентификаторов. –

+0

Я * полностью * согласен - но в некоторых случаях наши руки связаны. – Snekse

+0

Не могли бы вы рассказать нам, что вы не будете/не можете принять в качестве ответа? Я имею в виду, почему вы не можете использовать последовательность? – skirsch

ответ

2

Во-первых: это больно, чтобы ответить на это. Но я знаю, иногда вам приходится иметь дело с дьяволом :(

Технически, это не JPA, но если вы используете Hibernate в JPA-провайдера, вы можете пойти с

@org.hibernate.annotations.GenericGenerator(
    name = “incrementGenerator”, 
    strategy = “org.hibernate.id.IncrementGenerator”) 
@GeneratedValue(generator="incrementGenerator") 
private Long primaryKey; 

Раствор, Hibernate является «потокобезопасным», но не «безопасным для кластеров», то есть, если вы запускаете приложение на нескольких хостах, это может привести к сбою. Вы можете поймать соответствующее исключение и повторить попытку.

Если вы придерживаетесь своего решения : закрыть ResultSet, Statement и Connection. Извините, сначала не поймал try-in-resources.

+0

Извините, я должен был упомянуть, что мы используем EclipseLink. Мы пытаемся придерживаться строгого JPA API, но у нас уже была специфическая утечка API EclipseLink, поэтому я открыт для этого. Я буду копаться, чтобы увидеть, есть ли эквивалент EL, эквивалентный IncrementGenerator. – Snekse

0

Код JDBC является патологическим, не имеет смысла и не будет работать в многопользовательской среде.

Я настоятельно рекомендовал бы установить код для использования объекта последовательности или таблицы последовательности.

В JPA вы можете просто использовать последовательность.

See, http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Sequencing

Если вы действительно хотите сделать свои собственные последовательности, вы можете либо назначить Id самостоятельно, использовать PrePersist назначить свой собственный идентификатор, или в EclipseLink реализовать свой собственный Sequence подкласс, который делает все, что вы желание. Вам нужно будет зарегистрировать этот объект Sequence с помощью SessionCustomizer.

See, http://wiki.eclipse.org/EclipseLink/Examples/JPA/CustomSequencing

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