Question

I am trying to get Hibernate (v 4.2.3) to validate (hbm2ddl.auto = validate) my 4 existing DB tables when the application starts up. Here are my table create SQL scripts (this is an H2 DB):

-- Lookup/reference table, example records might be for ADVERB, NOUN,
-- VERB, etc.
CREATE TABLE word_types (
    word_type_id BIGINT AUTO_INCREMENT,
    word_type_label VARCHAR(100) NOT NULL,
    word_type_description VARCHAR(100) NOT NULL,
    word_type_tag VARCHAR(100) NOT NULL,
    CONSTRAINT uc_tag UNIQUE (word_type_tag)
);

-- A word in the English language. length is the number of chars in the
-- word, type ID is the word_types#word_type_id above (foreign key),
-- text is the actual word itself "quick", "fast", etc.
CREATE TABLE words (
    word_id BIGINT AUTO_INCREMENT,
    word_length INTEGER NOT NULL,
    word_type_id INTEGER NOT NULL,
    word_text VARCHAR(100) NOT NULL,
    word_definition VARCHAR(1000) NOT NULL,
    CONSTRAINT fk_word_types FOREIGN KEY (word_type_id) REFERENCES word_types(word_type_id),
    CONSTRAINT uc_text_type UNIQUE (word_text, word_type_id)
);

-- Crosswalk/junction table holding a many-to-many relationships between
-- pairs of words. Example: fast is a synonym of quick. So there would be
-- a words record for fast, and a words record for quick, and a record in
-- this table linking the 2 together.
CREATE TABLE synonyms (
    synonym_id BIGINT AUTO_INCREMENT,
    base_word_id INTEGER NOT NULL,
    has_synonym_id INTEGER NOT NULL,
    CONSTRAINT fk_word_1_base_id FOREIGN KEY (base_word_id) REFERENCES words(word_id),
    CONSTRAINT fk_word_synonym_id FOREIGN KEY (has_synonym_id) REFERENCES words(word_id),
    CONSTRAINT uc_syn_id_sets UNIQUE (base_word_id, has_synonym_id)
);

-- Same as above except this table relates words that are antonyms of
-- each other.
CREATE TABLE antonyms (
    antonym_id BIGINT AUTO_INCREMENT,
    base_word_id INTEGER NOT NULL,
    has_antonym_id INTEGER NOT NULL,
    CONSTRAINT fk_word_2_base_id FOREIGN KEY (base_word_id) REFERENCES words(word_id),
    CONSTRAINT fk_word_antonym_id FOREIGN KEY (has_antonym_id) REFERENCES words(word_id),
    CONSTRAINT uc_ant_id_sets UNIQUE (base_word_id, has_antonym_id)
);

Hence, 4 tables: words, synonyms & antonyms (which hold many-to-many relationships between different words) and a lookup/reference table word_types (such as ADVERB, NOUN, etc.). To clarify, if there is a words record with a word_text value of "quick", and another words/word_text record/value of "fast", then there may be an entry in the synonyms table where the base_word_id is "quick"'s ID, and has_synonym_id might be "fast"'s ID; because quick has a synonym called fast. Here is the Java model I want to use for these tables:

public class BaseModel {
    protected Long id;

    public Long getId() {
        return id;
    }

    public void setId(final Long id) {
        this.id = id;
    }
}

public class Word extends BaseModel {
    private String text;
    private Integer length;
    private WordType type;
    private String definition;
    private List<Word> synonyms;
    private List<Word> antonyms;

    // Getters, setters, ctors omitted for brevity...
}

public class BaseLookup extends BaseModel {
    private String label;
    private String description;
    private String tag;

    // Getters, setters, ctors omitted for brevity...
}

public class WordType extends BaseLookup {
    public WordType(String label, String description, String tag) {
        super(label, description, tag);
    }
}

So BaseModel provides each model with an ID. BaseLookup provides three fields/columns that all lookup tables will have, at a minimum. Word is pretty straight-forward, and WordType is a lookup wrapper that doesn't add any additional fields over its parent. However it may be very conceivable to one day have a BaseLookup subclass that does add fields beyond the label/description/tag fields that BaseLookup provides.

So I'm trying to figure out which annotations I need to add to each of my classes so that Hibernate is configured correctly to use both my Java and data models, and I'm running into some brick walls. Here is the best I've been able to come up with:

// This class doesn't translate into a table; it's just a base class that provides
// an ID for all other entities, and perhaps (down the road) other common fields as
// well.
public class BaseModel {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    protected Long id;

    public Long getId() {
        return id;
    }

    public void setId(final Long id) {
        this.id = id;
    }
}

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name="words")
public class Word extends BaseModel {
    // How do I force Word.getId() to be "words_id"?

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

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

    // But how do I make this the ID of a word_types record?
    @Column(name="word_type_id")
    private WordType type;

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

    // The words table doesn't have any synonyms or antonyms.
    // Rather there is a many-to-many relationship between
    // a word and its synonyms and its antonyms...
    @Column(name="???")
    private List<Word> synonyms;

    @Column(name="???")
    private List<Word> antonyms;

    // Getters, setters, ctors omitted for brevity...
}

// Not sure what to annotate this table with, because there is not
// base_lookup table or anything like that...
public class BaseLookup extends BaseModel {
    private String label;
    private String description;
    private String tag;

    // Getters, setters, ctors omitted for brevity...
}

// Furthermore, here, in the case of WordType, I'd like to force the parent
// fields to be "word_type_label", "word_type_description", and "word_type_tag";
// however, other BaseLookup subclasses should be able to force those same fields
// to map/bind to other tables with other field names.
//
// For example, I might some day want a Color POJO relating to a colors table with
// the following fields: color_label, color_description and color_tag, etc.
public class WordType extends BaseLookup {
    // How do I force WordType.getId() to be word_type_id?

    public WordType(String label, String description, String tag) {
        super(label, description, tag);
    }
}

Can some battle-weary Hibernate veteran help me correctly annotate my POJO classes/fields so that Hibernate will accommodate both my Java and data models? Specifically, I need solutions for:

  1. How to make BaseModel#id the ID for all other entities, but to appear as a unique column with a unique column name for each entity (word_id, word_type_id, color_id`, etc.).
  2. How to annotate the Word#type field so that Hibernate knows it is the word_type_id foreign key. Also, I need cascading to work in such a way that when I obtain a Word POJO instance from the DB, it is already populated with its WordType type.
  3. How to annotate Word#synonyms and Word#antonyms so that Hibernate stores their relationships in the crosswalk tables (of the same names).
  4. How to annotate WordType and BaseLookup such that Hibernate knows to look for a table called word_types with the following fields: word_type_label, word_type_description and word_type_tag. But, annotate them in such a way that I could also have other BaseLookup subclasses, like Color that might relate to a colors table with color_label, color_description and color_tag.

Thanks in advance!

Was it helpful?

Solution

I think you simply need to use @Entity from the starting point (in your inheritance hierarchy) when you have tables in DB and @MappedSuperClass if you just want to store JPA annotations for the inheritance hierarchy, without having DB tables (in your case for BaseModel and BaseLookup).

Also useful in your use case is the @AttributeOverride annotation in order to override the mapping information.

Besides, in order to set some mapping information that are part of a relationship, you use @JoinColumn in combination with one of the @ManyToMany, @ManyToOne, @OneToMany or @OneToOneannotations.

For answers to every of your 4 questions, see the bottom part of my response.

// This class doesn't translate into a table; it's just a base class that provides
// an ID for all other entities, and perhaps (down the road) other common fields as
// well.
@MappedSuperClass
public class BaseModel {
    @Id @GeneratedValue(strategy=GenerationType.AUTO)
    protected Long id;

    public Long getId() {
        return id;
    }

    public void setId(final Long id) {
        this.id = id;
    }
}

@Entity
@AttributeOverrides({
        @AttributeOverride(name="id", column=@Column(name="word_id"))
})
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Table(name="words")
public class Word extends BaseModel {
    // How do I force Word.getId() to be "words_id"?

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

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

    // But how do I make this the ID of a word_types record?
    //@Column(name="")
    @ManyToOne
    @JoinColumn(name="word_type_id", referencedColumnName="word_type_id")
    private WordType type;

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

    // The words table doesn't have any synonyms or antonyms.
    // Rather there is a many-to-many relationship between
    // a word and its synonyms and its antonyms...
    @ManyToMany()
    //use the below annotation if you want to set the names of the columns
    //    @JoinTable(joinColumns = @JoinColumn(name="word_id")},//column in this entity
    //          inverseJoinColumns = {@JoinColumn(name="synonym_id")})//column in the table of the set.
    private List<Word> synonyms;

    //@Column(name="???")
    @ManyToMany()
    //use the below annotation if you want to set the names of the columns
    //    @JoinTable(joinColumns = @JoinColumn(name="word_id")},//column in this entity
    //          inverseJoinColumns = {@JoinColumn(name="antonym_id")})//column in the table of the set.
    private List<Word> antonyms;

    // Getters, setters, ctors omitted for brevity...
}

// Not sure what to annotate this table with, because there is not
// base_lookup table or anything like that...
@MappedSuperClass
public class BaseLookup extends BaseModel {
    private String label;
    private String description;
    private String tag;

    // Getters, setters, ctors omitted for brevity...
}

// Furthermore, here, in the case of WordType, I'd like to force the parent
// fields to be "word_type_label", "word_type_description", and "word_type_tag";
// however, other BaseLookup subclasses should be able to force those same fields
// to map/bind to other tables with other field names.
//
// For example, I might some day want a Color POJO relating to a colors table with
// the following fields: color_label, color_description and color_tag, etc.
@Entity
    // How do I force WordType.getId() to be word_type_id?
    // this is how:
@AttributeOverrides({
    @AttributeOverride(name="id", column=@Column(name="word_type_id")),
    @AttributeOverride(name="label", column=@Column(name="word_type_label")),
    @AttributeOverride(name="description", column=@Column(name="word_type_description")),
    @AttributeOverride(name="tag", column=@Column(name="word_type_tag"))
})
public class WordType extends BaseLookup {


    public WordType(String label, String description, String tag) {
        super(label, description, tag);
    }
}

And now to answer your questions:

1.How to make BaseModel#id the ID for all other entities, but to appear as a unique column with a unique column name for each entity (word_id, word_type_id, color_id`, etc.).

Use @AttributeOverrides on classes that extend classes annotated with @MappedSuperClass (they are not entities, thus not mapped to DB tables).

2.How to annotate the Word#type field so that Hibernate knows it is the word_type_id foreign key. Also, I need cascading to work in such a way that when I obtain a Word POJO instance from the DB, it is already populated with its WordType type.

Use @ManyToMany-like annotations. Loading of WordType is made automatically. You might consider the fetch=FetchType.LAZY parameters in the @ManyToMany-like annotations for the opposite effect.

3.How to annotate Word#synonyms and Word#antonyms so that Hibernate stores their relationships in the crosswalk tables (of the same names).

Use @ManyToMany in combination with @JoinTable (if needed)

4.How to annotate WordType and BaseLookup such that Hibernate knows to look for a table called word_types with the following fields: word_type_label, word_type_description and word_type_tag. But, annotate them in such a way that I could also have other BaseLookup subclasses, like Color that might relate to a colors table with color_label, color_description and color_tag.

The same as 1.

PS: In JPA you MUST have the default constructor in every entity, in the case there is no one (in your WordType entity). Besides you might consider the advice from comments related to making abstract some classes and using the singular in your table names. Although you didn't explicitly addressed the question with Uniqueness of some columns: see this response for details how to make that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top