Now I have two tables, the first table called StudentBase and has three columns: id, firstname and lastname. The second table called ResearchAssistant and has two columns: id and course. I designed the tables like this because there are different kinds of students and research assistant is one of them. The two table could be joint together with the primary key id.
I'm writing an endpoint /researchAssistant and take following content as request body of POST method.
{
"firstname":"Jack",
"lastname":"Peter",
"course":"MATH"
}
What I want is that saving firstname and lastname into StudentBase table and save course into ResearchAssistant table. And generate a same id for both.
The first idea comes to my mind is building 3 model classes: StudentBase(id, firstname, lastname), ResearchAssistant(id, course) and ResearchAssistantMixed(firstname, lastname, course). I use ResearchAssistantMixed class as the request body class. After getting the data I will seperate it into a new StudentBase object and a ResearchAssistant object, then I store them seperately.
This process seems really stupid and the performance should be quite low. Do you have some better ideas? How does Spring Boot deal with such cases? Thank you!
This is a database problem and not a spring-boot problem. This is how I would approach (I'm assuming you're using some relational DB like MySql and hibernate for ORM):
Database Tables:
student_base
- id (primary key)
- first_name
_ last_name
research_assistant
- id (primary key)
- student_base_id (foreign key referencing id of student_base)
- course
You can now have equivalent entity classes in Java (for hibernate):
#Entity
#Table(name = "student_base")
public class StudentBase {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
// getters and setters
}
#Entity
#Table(name = "research_assistant")
public class ResearchAssistant {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne(optional = false)
#JoinColumn(name = "student_base_id")
private StudentBase studentBase;
#Column(name = "course")
private String course;
// getters and setters
}
Now in your DAOs, you don't need to do much, just persist a student_base record and use the returned object to persist a research_assistant record. For example:
StudentBase studentBase = persist(new StudentBase(1, "abc", "xyz");
persist(new ResearchAssistant(1, studentBase, "pqr");
You can (and should) have two separate classes to accept the request object of the post API (don't use entity classes to accept request data).
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:
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.).
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.
How to annotate Word#synonyms and Word#antonyms so that Hibernate stores their relationships in the crosswalk tables (of the same names).
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!
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.
I'm using the javax.validation API in my project and have the titular problem on some of developer machines and on the test server. On other developer machines this bug can't be reproduced despite the fact that we are using the same Glassfish version, IDE and library stack handled by maven.
While debugging this problem we suspected something that may be the cause of the problem (we can't debug inside compiled classes, therefore 'suspected') - we have an interface for our persistable entities:
public interface PersistableEntity<T> {
public T getId();
public void setId(T id);
}
Implementation classes are split by 'id' field type: String or BigDecimal. The #Pattern constraint is attached to the getter of the id field in the PersistableEntity<String> implementation.
If this is the cause, is it possible to maintain validation using javax.validation and use generics in the same time?
Update
We decided to move from JSR303 to custom configurable validation mechanism. If someone with titular problem finds it's solution, please let me know which one it is, and I'll mark it as an answer (any other solutions to this dilema from experienced SO users are welcome).
I'm usually using the Persistable interface of Spring Data, but the idea seems to be the same as your custom one.
Let's say my user table has two fields: user_id (PK) and email (NN). The JAVA User entity will then have a getUserId() getter and a getEmail() one.
When implementing the Persistable interface, a getId() getter must be overridden, while the table user doesn't contain an id field. You can explicitly tell JAVA that the corresponding table doesn't contain such a field by using the #Transient annotation:
#Entity
#Table(name = "user")
public class User implements Persistable<Integer> {
// Persistable implementation
#Override
#Transient // <-- means "not a database field"
public Integer getId() {
return getUserId();
}
// user_id
#Column(name = "user_id", precision = 10)
#GeneratedValue
#Id
public Integer getUserId() {
return userId;
}
// email
#NotNull
#Column(name = "email", nullable = false, unique = true)
public String getEmail() {
return email;
}
}
Maybe this could solve your problem.
I have a generic Database structure which can store several user-defined records. For example, the main table is RECORD and the columns are STRING01, STRING02, [...], NUM01, NUM02 etc.
I know this is a bit weird, but it has advantages as well as disadvantages. However, this structure exists and can't be changed. Now I want to create some JPA classes.
First, I created an abstract class RECORD as follows (the Annotations are placed on the gettersthe example is just simplified):
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE", discriminatorType=DiscriminatorType.STRING)
public abstract class Record {
#Id
private long id;
#Column(name="STRING01")
private String string01;
#Column(name="STRING02")
private String string02;
#Column(name="NUM01")
private BigDecimal num01;
}
Then, I created specific classes inherited from RECORD:
#Entity
#DiscriminatorValue("Person")
public class Person extends Record {
#Transient
public String getFirstName() {
return getString01();
}
public void setFirstName(String name) {
setString01(name);
}
#Transient
public BigDecimal getWeight() {
return getNum01();
}
public void setWeight(BigDecimal weight) {
setNum01(weight);
}
}
This works fine, as I can query RECORD for a PERSON's primary key (via EntityManager.find()) and get a Result as instance of PERSON. I can query for FirstName and Weight without having to know the generic column names.
However, if I write my own JPA Query like SELECT p FROM Person p WHERE p.firstName = 'Michael', it fails. firstName is transient, and here I have to use the generic name string01.
Is there some way of overriding the base class' attribute name in JPA? Maybe there's a vendor-specific solution (I'm using EclipseLink)?
You can try and map multiple attributes to the same column.
The additional attribute would then be annotated with #Column( name = "column name", insertable = false, updatable = false, nullable = false ).
Alternatively, you might be able to replace/enhance the JPQL resolver in order to internally map p.firstName to p.string01, but that would be EclipseLink specific, and I don't really know if that's even possible. Take this as just a hint what to look for.
I'm currently trying to create a pair of Hibernate annotated classes to load (read only) from a pair of tables in a legacy system. The legacy system uses a consistent (if somewhat dated) approach to keying tables. The tables I'm attempting to map are as follows:
Customer CustomerAddress
-------------------------- ----------------------------
customerNumber:string (pk) customerNumber:string (pk_1)
name:string sequenceNumber:int (pk_2)
street:string
postalCode:string
I've approached this by creating a CustomerAddress class like this:
#Entity
#Table(name="CustomerAddress")
#IdClass(CustomerAddressKey.class)
public class CustomerAddress {
#Id
#AttributeOverrides({
#AttributeOverride(name = "customerNumber", column = #Column(name="customerNumber")),
#AttributeOverride(name = "sequenceNumber", column = #Column(name="sequenceNumber"))
})
private String customerNumber;
private int sequenceNumber;
private String name;
private String postalCode;
...
}
Where the CustomerAddressKey class is a simple Serializable object with the two key fields. The Customer object is then defined as:
#Entity
#Table(name = "Customer")
public class Customer {
private String customerNumber;
private List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
private String name;
...
}
So, my question is: how do I express the OneToMany relationship on the Customer table?
I may have an answer for my own question. Add the following to Customer:
#OneToMany(mappedBy="customer")
#JoinColumn(name="customerNumber")
List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
And the following to CustomerAddress:
#ManyToOne
#JoinColumn(name="customerNumber")
protected Customer customer;
This turns out to be a simpler problem to solve than I first thought. Typical.
I assume you have read the Bauer/King Hibernate book, which is extremely bad at explaining how do implement composite primary keys correctly. Don't let yourself be fooled by a bad book: composite primary key are not a property of legacy systems...