1

Using Hibernate 4.2.3 final. I have the following entity:

@Entity
@AttributeOverrides({
    @AttributeOverride(name="id", column=@Column(name="word_id"))
})
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name="words")
public class Word {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    protected Long id;

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

    @Column(name="word_length")
    private Integer length;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="word_type_id", referencedColumnName="word_type_id")
    private WordType type;

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

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "synonyms", joinColumns = @JoinColumn(name = "word_id"), inverseJoinColumns = @JoinColumn(name = "synonym_id"))
    private List<Word> synonyms;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "antonyms", joinColumns = @JoinColumn(name = "word_id"), inverseJoinColumns = @JoinColumn(name = "antonym_id"))
    private List<Word> antonyms;

    // ctor, getters/setters, etc.
}

And the following DAO for the entity:

public class WordDAO {
    public Word saveWord(Word word) {
        Session session = getDaoUtils().newSessionFactory().openSession();
        Word returnable = null;
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();

            session.saveOrUpdate(word);
            returnable = word;

            transaction.commit();
        } catch(Throwable throwable) {
            transaction.rollback();
            throw new RuntimeException(throwable);
        } finally {
            session.close();
    }

        // Return any result, if applicable.
        return returnable;
    }
}

And the following test driver:

public class HibernateTester {
    public static void main(String[] args) {
        Word fast = new Word("fast", 4, WordType.Adverb, "A fast thing.", new ArrayList<Word>(), new ArrayList<Word>());
        Word slow = new Word("slow", 4, WordType.Adverb, "A slow thing.", new ArrayList<Word>(), new ArrayList<Word>());
        Word quick = new Word("quick", 5, WordType.Adverb, "A quick thing.", new ArrayList<Word>(), new ArrayList<Word>());

        quick.addSynonym(fast);
        quick.addAntonym(slow);

        WordDAO wordDAO = new WordDAO();

        wordDAO.saveWord(quick);
    }
}

If I run HibernateTester multiple times, I it inserts the 3 words into my DB tables each time. So if I delete every record from my words table, and then run the test driver 4 times, I'll have 12 words in the table (4 runs x 3 records/run). Since I'm using Session#saveOrUpdate, I would have expected Hibernate to be smart enough to figure out that the entities already exist in the DB, and prevent them from being inserted.

Why is this happening? And more importantly what is a viable solution? How do I configure Hibernate not to insert dupes across multiple driver runs?

halfer
  • 19,824
  • 17
  • 99
  • 186
IAmYourFaja
  • 55,468
  • 181
  • 466
  • 756
  • Further to explanation by JB Nizet you could prevent this by ditching your auto generated ID and declaring a composite ID consisting of the text and type fields.See: http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_and_ManyToOne_Relationships – Alan Hay Nov 13 '13 at 22:33
  • Ouch, don't do that. It's horrible. Always prefer purely technical, auto-generated primary keys. They make everything easier, cleaner and faster. – JB Nizet Nov 13 '13 at 22:36
  • But it is a viable solution? – Alan Hay Nov 13 '13 at 22:40
  • It all depends on what you mean by viable. Renaming 1000 files one by one is a viable solution, but automating the task in a script is a way better one. Functional PKs are bad. Composite, functional PKs are even worse. – JB Nizet Nov 13 '13 at 22:42
  • They're not something I tend to use myself. Some reading for tomorrow if its another quiet day at the office! "Religious wars have been, and still are, going on on this subject." http://stackoverflow.com/questions/963809/should-i-use-composite-primary-keys-or-not – Alan Hay Nov 13 '13 at 22:51

1 Answers1

4

saveOrUpdate() saves the entity if it doesn't have an ID, and updates it if it already has an ID. You always pass entities which don't have an ID, so Hibernate creates the entity. every time.

The only thing that identifies an entity is its ID. Not its name, text, or whatever other attribute.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Thanks @JBNizet (+1) - so what's the solution here? Add a DAO method like `WordDAO#getWordByText(String`), and look up each word first? That way each word would have an ID, and so when I go to save it, it would be treated as an update... is that a viable solution? What's standard practice here? Thanks again! – IAmYourFaja Nov 13 '13 at 22:30
  • What you should do depends on what you want to achieve. What do you want to achieve? – JB Nizet Nov 13 '13 at 22:32
  • I want to make sure that dupes don't make it into my DB. The `words` table should have a uniqueness constraint on `word_text` and `word_type_id`. So if I wanted to fetch a word out of the DB, then I guess I would have to have a method like: `WordDAO#getWord(String wordText, WordType wordType) : Word`, and it might be invoked like so: `Word fox = wordDAO.getWord("fox", WordType.NOUN);`. At this point `fox` should have a populated ID, and if I make any changes to it (or not) and then call `wordDAO.saveWord(fox)`, then it should be updated, not inserted... – IAmYourFaja Nov 13 '13 at 22:37
  • ...and this is the best solution I can think of, but this is my very first Hibernate project. So I'm not sure if there is a better way of preventing dupes or not... – IAmYourFaja Nov 13 '13 at 22:37
  • 1
    That's the way to do it. But you should also have a unique constraint in the database to enforce that at the database level (which is the only one that can guarantee the uniqueness). Note that you don't need to call WordDAO.saveWord(fox). Any change you make to an attached entity is automatically made persistent, without having to call any save method. You just find the entity in the database, and update it, and that's all. – JB Nizet Nov 13 '13 at 22:46
  • Hmmmmm, thanks again @JBNizet, but that actually concerns me. Any way to turn that functionality off. Maybe its because I'm new to Hibernate, but I would prefer to specifically tell the DB when I want to persist any changes to my entities... – IAmYourFaja Nov 13 '13 at 22:54
  • No, there's no (easy) way. Don't fight the tool. Learn it, and use it as it's intended to be used. – JB Nizet Nov 13 '13 at 22:55