JPA: Как добавить первую сущность рекурсивного ненулевого отношения - PullRequest
2 голосов
/ 01 апреля 2011

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

Проблема возникает при попытке использовать мою контрольную защиту по умолчанию (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 и исключение root:

 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 тоже.

1 Ответ

1 голос
/ 01 апреля 2011

Не пустое ограничение требует, чтобы вы вставили 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;
  }
}

In 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);

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...