JPA: как добавить первую сущность рекурсивного ненулевого отношения

Как добавить первую сущность рекурсивного ненулевого отношения?

Проблема возникает при попытке использовать мою контрольную защиту по умолчанию (EntityListener) для пользовательских сущностей. Hibernate не может вставить первого пользователя. Пример упрощения ситуации:

@Entity
class User {
    @Id @GeneratedValue
    private Long id;
    @Basic
    private String name;
    @ManyToOne(optional=false)
    @JoinColumn(nullable=false,updatable=false)
    private User createdBy;

    // getters & setters
    // equals & hashcode are based on name
}

И я пробовал что-то вроде:

User user = new User();
user.setName( "Some one" );
user.setCreatedBy( user );

Hibernate не работает, и корневое исключение:

 com.microsoft.sqlserver.jdbc.SQLServerException: Cannot insert the value NULL into 
column 'createdby_id', table 'mydb.user'; column does not allow nulls. INSERT fails.

Я знаю несколько обходных путей: вставьте первую сущность вручную (вставка SQL) или установите значение nullable = true. Во-первых, это просто раздражает, а во-вторых, это точка отказа целостности (требуется настраиваемый прослушиватель аудита и триггер базы данных). Есть варианты получше? Или, другими словами: есть ли решение только для JPA?

Моя платформа: SQL Server 2008 и Hibernate 3.6 в качестве поставщика JPA2.

Изменить: сначала я использовал persist(). Я попробовал merge(), что могло дать более разумное предположение, но без разницы. И я тоже пробовал CascadeType.MERGE.


person Antti Pihlaja    schedule 01.04.2011    source источник


Ответы (1)


Ограничение not null требует, чтобы вы вставляли user.id в поле ссылки, но если вы установили автоматическое создание поля id, это значение будет известно только после вставки. Чтобы решить эту проблему, вы можете использовать последовательность для генерации @Id

create sequence SEQ_USER

и вставьте первого пользователя, выбрав следующее значение этой последовательности и установив это значение в обоих полях. С собственным SQL-запросом из JPA это выглядит так:

User u = new User();
u.setName("First User");
Query q = em.createNativeQuery("VALUES nextval for SEQ_USER");
Long nextId = Long.valueOf((Integer) q.getSingleResult());
q = em.createNativeQuery("insert into user (id, name, created_by) values (?, ?, ?)");
q.setParameter(1, nextId).setParameter(2, u.getName()).setParameter(3, nextId);
q.executeUpdate();
// don't forget to bring u into persistence context
u = em.find(User.class, nextId);

который немного неуклюжий, но должен вызываться только один раз. Все остальные пользователи будут сохраняться обычным способом (используя SEQ_USER в качестве последовательности генератора для @Id)

В качестве альтернативы вы можете написать свой собственный SequenceGenerator, который сделает именно это:

public class UserIdGenerator implements IdentifierGenerator {

  @Override
  public Serializable generate(SessionImplementor session, Object object) throws HibernateException {

    User o = (User) object;
    Connection connection = session.connection();
    try {
      PreparedStatement ps = connection.prepareStatement("VALUES nextval for SEQ_USER");
      ResultSet rs = ps.executeQuery();
      if (rs.next()) {
        return rs.getLong(1);
      }
    } catch (SQLException e) {
      log.error("Unable to generate Id: " + e.getMessage(), e);
    }
    return null;
  }
}

В User вы можете установить этот генератор как:

@Id
@GenericGenerator(name = "idSource", strategy = "com.example.db.UserIdGenerator", parameters = { @Parameter(name = "sequence", value = "SEQ_USER") })
@GeneratedValue(generator = "idSource")
private Long id;

а потом

User u = new User();
u.setName("First User");
u.setCreatedBy(u);
em.persist(u);

работает как положено.

person wallenborn    schedule 01.04.2011
comment
Обновил исходный пост: @GeneratedValue раздели. Как вы упомянули, проблема заключается в сгенерированной идентичности. Но в SQL Server нет последовательностей. SQL Insert выглядит так: insert into user ( name, createdBy ) values ('john doe',scope_identity());. Но в реальном классе User 13 ненулевых столбцов ... - person Antti Pihlaja; 01.04.2011
comment
Вы можете попытаться смоделировать последовательность, ср. это: stackoverflow.com/questions/282943/. Или вы можете полностью перенести генерацию идентификаторов из базы данных на уровень JPA, возможно, используя UUID в качестве первичных ключей для таблицы User, как описано здесь: onjava.com/pub/a/onjava/2006/09/13/ < / а> - person wallenborn; 02.04.2011