سؤال

I've got a problem preventing the duplicated values from being inserted into MySQL database (using Hibernate). The problem is: I have Category, which has parent Category. There can be two categories with same name, but different parents (that's why the category name cannot be used as businessId). When the category has no parent (i.e top level categories) the value of parentId is NULL. I'm unable to understand what I'm doing wrong in the following code to get the duplicated inserted in database.

Here is my entity class.

@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;

    ...
}

Here is the code used to parse new categories (and I belive that something is wrong here :-|)

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

And here is the insertCategory function

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;
}

After executing the code I have got the following entries in database:

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

When I try to restrict only by "name", only 3 entires are inserted to database, but I cannot impose restriction by name only, because of the situation described above.

EDIT: one of the possibilities I was seeing to solve this issue was function usage in CONSTRAINT definitions (which in case of MySQL could be IFNULL(parentId,0). And it seems that such possibility is possible for example in Oracle (according to this post), but not in MySQL.

EDIT 2: actually I've found in MySQL bug tracker people with similar problem. Since I'm using MySQL, I've checked what code is being generated while creating table and it was almost identical to the one in Bug report (which actually considered NOT bug acording to some standarts, mentioned in bugtracker. Anyways, my collegue have tried to execute equivalent code on MS SQL Server and it actually prevented him from inserting the lines with same values on name field and null parentId. Seems like there is no database level possibility (at least using MySQL) to make desired CONSTRAINT, which is quite disappointing. Accepting the answer, since it's one of possible workarounds for the issue (using magic numbers, which I will not stick to, prefering to keep one extra find query in my code)

Any help will be apreciated, Thanks!

هل كانت مفيدة؟

المحلول

A simple solution is make parentId be "self".

+----+--------------+----------+
| id | name         | parentId |
+----+--------------+----------+
|  1 | A            |        1 |
|  2 | B            |        1 |
|  3 | C            |        2 |
|  4 | C            |        3 |
+----+--------------+----------+

So you can't never have two "top level" categories. Make parentId not nullable.

I'm sure your problem has something to do with null values not being checked in constraints. You can try with hibernate checks though, but it'll be certainly more inefficient.

@org.hibernate.annotations.Check
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top