Hibernate UniqueConstraint несколько столбцов с возможными значениями null


У меня есть проблема, препятствующая вставке дублированных значений в базу данных MySQL (используя Hibernate). Проблема в том, что у меня есть категория, которая имеет родительскую категорию. Могут существовать две категории с одинаковым именем, но разными родителями (поэтому название категории не может использоваться в качестве businessId). Если категория не имеет родителя (т. е. категории верхнего уровня), значение parentId равно NULL. Я не могу понять, что я делаю неправильно в следующем коде, чтобы получить дубликат вставлен в база данных.

Вот мой класс сущностей.

@Entity
@Table(name = "category", uniqueConstraints = {@UniqueConstraint(columnNames = {"name","parentId"})})
public class Category implements Serializable {
    @Id
    @Column(name="id")
    @GeneratedValue
    private long id;

    @Column(name="name")
    private String name;

    @ManyToOne(cascade={CascadeType.ALL})
    @JoinColumn(name="parentId", nullable=true)
    private Category parent;

    ...
}

Вот код, используемый для разбора новых категорий (и я считаю, что здесь что-то не так: -|)

 for (...) {
      Category parent = null;
      String [] categories = somePath.split("\\");

      for (String cat: categories) {
          Category category = new Category();
          category.setName(cat);
          category.setParent(parent);
          parent = categoryDB.insertCategory(category);
      }
 }

А вот функция insertCategory

public Category insertCategory (Category category) {
    Session session = sessionFactory.openSession();
    Transaction transaction = null;
    try{
        transaction = session.beginTransaction();
        category.setId((Long) session.save(category)); 
        transaction.commit();
    } catch (HibernateException e) {
        if (transaction!=null) 
            transaction.rollback();
        ...
    } finally {
        session.close(); 
    }
    return category;
}

После выполнения кода я получил следующие записи в базе данных:

select * from category;
+----+--------------+----------+
| id | name         | parentId |
+----+--------------+----------+
|  1 | A            |     NULL |
|  4 | A            |     NULL |
|  2 | B            |        1 |
|  3 | C            |        2 |
|  5 | B            |        4 |
|  6 | C            |        5 |
+----+--------------+----------+

Когда я пытаюсь ограничить только по" имени", в базу данных вставляются только 3 целого, но я не могу наложить ограничение только по имени, из-за описанной ситуации выше.

EDIT: одной из возможностей, которые я видел, чтобы решить эту проблему, было использование функций в определениях ограничений (которые в случае MySQL могут быть IFNULL(parentId, 0). И кажется, что такая возможность возможна, например, в Oracle (согласно этому посту), но не в MySQL.

EDIT 2 : на самом деле я нашел в MySQL bug tracker людей с подобной проблемой. Поскольку я использую MySQL, я проверил, какой код генерируется при создании таблицы и она была практически идентична той, что была в Bug report (которая фактически считалась не багом в соответствии с некоторыми стандартами, упомянутыми в bugtracker. В любом случае, мой коллега попытался выполнить эквивалентный код на MS SQL Server, и это фактически помешало ему вставить строки с одинаковыми значениями в поле name и null parentId. Похоже, что на уровне базы данных нет возможности (по крайней мере, с помощью MySQL) сделать желаемое ограничение, что весьма разочаровывает. Принятие ответа, поскольку это один из возможных обходных путей для решения проблемы (используя magic numbers, которого я не буду придерживаться, предпочитая сохранить один дополнительный запрос find в моем коде)

Любая помощь будет оценена по достоинству, Спасибо!

1 2

1 ответ:

Простое решение-заставить parentId быть "собой".

+----+--------------+----------+
| id | name         | parentId |
+----+--------------+----------+
|  1 | A            |        1 |
|  2 | B            |        1 |
|  3 | C            |        2 |
|  4 | C            |        3 |
+----+--------------+----------+
Таким образом, у вас никогда не может быть двух категорий "верхнего уровня". Сделайте parentId не подлежащим аннулированию.

Я уверен, что ваша проблема связана с тем, что нулевые значения не проверяются в ограничениях. Хотя вы можете попробовать с проверками hibernate, но это будет, безусловно, более неэффективно.

@org.hibernate.annotations.Check