I would like to implement inheritance in Hibernate.
I created ObjectClass object:
#Entity
#Table(name = "object")
#Inheritance(strategy = InheritanceType.JOINED)
public class ObjectClass {
private id;
}
and CodeTable object that inhertance Object class:
#Entity
#ForeignKey(name = "id")
#Table(name = "code_table")
public class CodeTable extends ObjectClass{
private String description;
}
in the database
object table is:
CREATE TABLE `object` (
`id` bigint(11) NOT NULL auto_increment,
PRIMARY KEY (`id`),
)
code_table table is:
-
CREATE TABLE `code_table` (
`id` bigint(11) NOT NULL auto_increment,
`description` varchar(45) character set latin1 default NULL,
PRIMARY KEY (`id`),
KEY `FK_object` (`id`),
CONSTRAINT `FK_object` FOREIGN KEY (`id`) REFERENCES `object` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
)
I wrote the following code to retreive data from codeTable:
#SuppressWarnings( "unchecked" )
#Transactional( readOnly = true, propagation = Propagation.REQUIRED )
public Collection<CodeTable> findAll() {
Session session = getSessionFactory().getCurrentSession();
return
session.createCriteria( persistentClass
).setResultTransformer( Criteria.DISTINCT_ROOT_ENTITY
).list();
}
I gets empty list although there is one record in codetable table.
When I write the following SQL in my database:
SELECT * FROM `code_table`
I get:
id= 1,
description = company.
What went wrong in my Hibernate definition? How can I retrieve the object?
EDITED:
My hibernate.cfg.xml file looks like this:
<hibernate-configuration>
<session-factory>
<mapping class="com.mycompany.model.CodeTable" />
<mapping class="com.mycompany.model.ObjectClass" />
</session-factory>
</hibernate-configuration>
Your mappings and table structure are (roughly) correct for a JOINED inheritance strategy and I cannot reproduce your problem.
I use the following mappings (which are basically the one you provided):
#Entity
#Table(name = "object")
#Inheritance(strategy = InheritanceType.JOINED)
public class ObjectClass {
#Id #GeneratedValue
private Long id;
public ObjectClass() { }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
And
#Entity
#ForeignKey(name = "id")
#Table(name = "code_table")
public class CodeTable extends ObjectClass{
private String description;
public CodeTable() { }
public String getDescription() { return description; }
public void setDescription(String description) {
this.description = description;
}
#Override
public String toString() {
return "CodeTable [getDescription()=" + getDescription() + ", getId()="
+ getId() + "]";
}
}
The following tables:
create table code_table (
description varchar(255),
id bigint not null,
primary key (id)
)
create table object (
id bigint not null,
primary key (id)
)
alter table code_table
add constraint id
foreign key (id)
references object
And the following parent/child records:
insert into object values (1);
insert into code_table(id, description) values (1, 'foo');
And running your criteria query:
session.createCriteria(CodeTable.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
Returns:
CodeTable [getDescription()=foo, getId()=1]
Everything works as expected.
References
JPA 1.0 Specification
2.1.10 Inheritance Mapping Strategies
Hibernate Annotations Reference Guide
2.2.4. Mapping inheritance
How does your mapping looks like ?
Have you read this section in the Hibernate doc ?
Inheritance mapping in Hibernate
As you can read in the link I provided above, your mapping is not correct. You have to let Hibernate know that the code_table class inherits from the object class, and you 'll have to let Hibernate know how this link exists in the database.
Related
I need to map with JPA the following legacy DB table structure I cannot change.
It’s a one to many relationship between table ao_rda_acq (1) -> ao_rda_acq_righe (many) (purchase requisition -> purchase requisition rows)
Table (1)
create table ao_rda_acq
(
id_divisione varchar(4) not null,
esercizio smallint not null,
id_rda varchar(10) not null,
...
other fields
...
constraint pk_ao_rda_acq
primary key (id_divisione, esercizio, id_rda)
)
table many
create table ao_rda_acq_righe
(
id_divisione varchar(4) ,
esercizio smallint not null,
id_rda varchar(10) not null,
nr_riga integer not null,
...
other fields
...
constraint pk_ao_rda_righe
primary key (id_divisione, esercizio, id_rda, nr_riga),
constraint ao_rda_acq_righe_ao_rda_acq_id_divisione_esercizio_id_rda_fk
foreign key (id_divisione, esercizio, id_rda) references ao_rda_acq
)
The primary key of table ao_rda_acq side one of the relationship has 3 fields id_divisione, esercizio, id_rda. The primay key of the table side many of the relationship has the same 3 filed plus a 4th field nr_riga.
I tryed with this JPA approch using #IdClass annotation for composite primary keys
#Table(name="ao_rda_acq")
#Entity
#IdClass(RdaId.class)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Rda {
#Id
public String idDivisione;
#Id
public Integer esercizio;
#Id
public String idRda;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name="id_divisione", referencedColumnName = "id_divisione"),
#JoinColumn(name="esercizio", referencedColumnName = "esercizio"),
#JoinColumn(name="id_rda", referencedColumnName = "id_rda")
})
#OrderBy("nrRiga")
public List<RdaRiga> righe = new ArrayList<>();
//Additional fields
}
where
public class RdaId implements Serializable {
String idDivisione;
Integer esercizio;
String idRda;
}
The entity for the rows is
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
#IdClass(RdaRigaId.class)
#Table(name = "ao_rda_acq_righe")
public class RdaRiga {
#Id
public String idDivisione;
#Id
public Integer esercizio;
#Id
public String idRda;
#Id
public Long nrRiga;
//More fields
}
where
public class RdaRigaId implements Serializable {
String idDivisione;
Integer esercizio;
String idRda;
Long nrRiga;
}
This code compiles but JPA at start-up complains with this message
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.DuplicateMappingException: Table [ao_rda_acq_righe] contains physical column name [id_divisione] referred to by multiple logical column names: [id_divisione], [idDivisione]
Is this approch the best one to map my unhappy DB structure? If so what did I miss?
When you use #Id without the #Column annotation, the name of the column is assumed to be the name of the annotated property.
Given that your DB column seems to be *id_divisione* you need to use also the annotation #Column(name = "id_divisione").
This also applies to the other properties annotated with #Id.
I have 2 classes extending a base class.
Questions.java
#Entity
#Table(name="question")
#Access(value = AccessType.FIELD)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Questions{
private static final long serialVersionUID = 1L;
private String qid;
#Column(name="addedtime")
private String addedtime;
#Column(name="qlang")
private String qlang;
#Id
#Column(name="qid")
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Access(value = AccessType.PROPERTY)
public String getQid() {
return qid;
}
public void setQid(String qid) {
this.qid = qid;
}
#Access(value = AccessType.PROPERTY)
public String getAddedtime() {
return addedtime;
}
public void setAddedtime(String addedtime) {
this.addedtime = addedtime;
}
#Access(value = AccessType.PROPERTY)
public String getQlang() {
return qlang;
}
public void setQlang(String qlang) {
this.qlang = qlang;
}
}
MCQ.java, TwoMarkQ.java - all 2 classes extend Question.java.
MCQ.java
#Entity
#Table(name="MCQ")
#Access(value = AccessType.FIELD)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class MCQ extends Questions implements Serializable{
#Column(name="option_1")
#Access(value = AccessType.FIELD)
private String option_1;
#Access(value = AccessType.PROPERTY)
public String getOption_1() {
return option_1;
}
public void setOption_1(String option_1) {
this.option_1 = option_1;
}
#Column(name="option_2")
#Access(value = AccessType.FIELD)
private String option_2;
#Access(value = AccessType.PROPERTY)
public String getOption_2() {
return option_2;
}
public void setOption_2(String option_2) {
this.option_2 = option_2;
}
}
TwoMarkQ.java
#Entity
#Table(name="TwoMarkQ")
#Access(value = AccessType.FIELD)
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class TwoMarkQ extends Questions implements Serializable{
#Column(name="option_1")
#Access(value = AccessType.FIELD)
private String option_1;
#Access(value = AccessType.PROPERTY)
public String getOption_1() {
return option_1;
}
public void setOption_1(String option_1) {
this.option_1 = option_1;
}
#Column(name="option_2")
#Access(value = AccessType.FIELD)
private String option_2;
#Access(value = AccessType.PROPERTY)
public String getOption_2() {
return option_2;
}
public void setOption_2(String option_2) {
this.option_2 = option_2;
}
}
All these 3 tables are mapped to unique tables in MySQL database.
Following are the results for show create table for each table
create table `question` (
`qid` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
`addedtime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`qtype` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`qlang` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`qid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
create table `MCQ`(
`qid` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
`option_1` bigint(20) DEGAULT `0`,
`option_2` bigint(20) DEGAULT `0`,
PRIMARY KEY (`qid`),
CONSTRAINT `mcq_ibfk1` FOREIGN KEY (`qid`) REFERENCES `question` (`qid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
create table `TwoMarkQ`(
`qid` varchar(48) COLLATE utf8mb4_unicode_ci NOT NULL,
`option_1` bigint(20) DEGAULT `0`,
`option_2` bigint(20) DEGAULT `0`,
PRIMARY KEY (`qid`),
CONSTRAINT `two_markq_ibfk1` FOREIGN KEY (`qid`) REFERENCES `question` (`qid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
In one of the DAO class, the SQL query goes like this.(The SQL query is against a derived class)
Query query = session.createQuery("select q.qid, q.qtype from Questions q where q.qlang=:lang ORDER BY q.addedtime ASC");
query.setParameter("lang", lang);
query.setFirstResult(startingRow).setMaxResults(10);
result = (List<Questions>) query.list();
Error happens in the above line result = (List<Questions>) query.list();
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown
column 'qid' in 'field list'
Questions
1) Why am I getting com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'addedtime' in 'field list' and how to fix it?
Please help. Struck on this for 3 days.
PS: I'm using Hibernate version 4.3.5.Final
PS: This is the query generated
select questions0_.qid as col_0_0_, questions0_.qtype as col_1_0_ from ( select qid, addedtime, qlang, qtype, null as option_1, null as option_2 as class_ from MCQ union select qid, addedtime, qlang, qtype, null as option_!, null as option_2 as class_ from TwoMarkQ) questions0_ where questions0_.qlang=? order by questions0_.addedtime ASC limit ?
Since the Query query = session.createQuery("select q.qid, q.qtype from Questions q where q.qlang=:lang ORDER BY q.addedtime ASC"); is on base class, it looks like it is making union with all the sub classes and sub classes doesn't have addedtime column. I'm just guessing.
then the error is due to the fact that in the table where hibernate is mapping there is no column, ie the field that usually defoult takes the name of the property.
The first question I want to ask you is the table is already present on the DB ?? why do you hibenate before doing anything you need to know with who you are mapping and in normal Hibernate use the classes let themselves be mapped to him.
So for when I can find out about hibernate to solve the problem you are redeeming you should map a single class with the one you have on the DB and then later map the "class of support" to your actual model.
Type a solution that is used when taking data from a server and the data does not reflect your current model.
I hope I have responded in line with your question and have been of help.
PS: Try to check if your db hibernate has not created other relational tabbells.
I try to post a bit of my code in hibernate version 4.x.x, why instead of writing the query do not try to use the Creteria and the Restrictions? the creteria gives you a list that you can decide if you want to return it already ordered, for example in my banal code of a person's search I had a generic DAO that implemet the query through the creteria.
//DAO Generic
public List<T> findByCriteria(Criterion... criterion) throws DAOException {
try {
Criteria crit = getSession().createCriteria(getPersistentClass());
for (Criterion c : criterion) {
crit.add(c);
}
return crit.list();
} catch (HibernateException ex) {
logger.error(ex.getLocalizedMessage());
throw new DAOException(ex);
}
}
//DAO person
public List<Persona> findByCognome(String cognome) throws DAOException {
try {
Criteria criteria = getSession().createCriteria(Persona.class);
criteria.add(Restrictions.eq("cognome", cognome).ignoreCase());
criteria.addOrder(Order.asc("eta"));
return criteria.list();
} catch (DAOException ex) {
throw new DAOException(ex.getLocalizedMessage());
}
}
Change the inheritance strategy to JOINED.
#Inheritance(strategy = InheritanceType.JOINED).
You might need to add a column for question type in question table which is discriminator column for the type of question which you need to put as annotation on Questions class.
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "QUESTION_TYPE")
public class Questions{
.
.
}
And, in the child classes, you can give the discriminator value using #DiscriminatorValue annotation.
#DiscriminatorValue("MCQ")
public class MCQ extends Questions implements Serializable{
.
}
and
#DiscriminatorValue("TwoMarkQ")
public class TwoMarkQ extends Questions implements Serializable{
.
}
and remove #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) from child classes.
Ok, This is how I resolved this issue.
The root cause is extending the base class Question.
I created a new class called Ques, which is implemented by Question.java, MCQ.java and TwoMarkQ.java
As per Hibernate documentation
We are provided with 3 options.
InheritanceType.TABLE_PER_CLASS or InheritanceType.SINGLE_TABLE or InheritanceType.JOINED
JOINED: is definitely not what I wanted. so it is a ruled out option.
SINGLE_TABLE:
The single table inheritance strategy maps all subclasses to only one database table.So this also leads to union of sub classes extending base class.
I still have following
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#Polymorphism(type=PolymorphismType.EXPLICIT)
on Question.java, MCQ.java and TwoMark.java
TABLE_PER_CLASS
"When using polymorphic queries, a UNION is required to fetch the base class table along with all subclass tables as well." - So this is also a ruled out option.
Moreover I removed the foreign key references between the tables.
In my db I have two tables which look like this:
CREATE TABLE IF NOT EXISTS `Lokal` (
`idLokal` int(11) NOT NULL AUTO_INCREMENT,
`Ocena_idOcena` int(11) NOT NULL,
PRIMARY KEY (`idLokal`,`Ocena_idOcena`),
KEY `fk_Lokal_Ocena_idx` (`Ocena_idOcena`)
)
CREATE TABLE IF NOT EXISTS `Ocena` (
`idOcena` int(11) NOT NULL AUTO_INCREMENT,
`Ocena` int(1) DEFAULT NULL,
PRIMARY KEY (`idOcena`)
)
I want to map my Lokal entity to this Ocena table using #SecondaryTable Hibernate annotation, what I managed to achieve is this:
#Entity
#Table(name="Lokal")
#SecondaryTable(name = "Ocena", pkJoinColumns=#PrimaryKeyJoinColumn(name="Ocena_idOcena"))
public class Lokal {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="idLokal")
private int id;
#Column(table="Ocena" ,name="idOcena")
private int rating;
//--Getters and Setters skipped--//
}
But all I get is an error saying:
ERROR: Unknown column 'this_1_.Ocena_idOcena' in 'on clause'
I think I'm misunderstanding the #SecondaryTable annotation, but this is my first Spring/Hibernate application so I'd be glad for any kind of help.
Try this:
#Entity
#Table(name="Lokal")
#SecondaryTable(name = "Ocena", pkJoinColumns=#PrimaryKeyJoinColumn(name="idOcena"))
public class Lokal {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="idLokal")
private int id;
#Column(table="Ocena" ,name="rating")
private int rating;
//--Getters and Setters skipped--//
}
I don't see an example anywhere so I am not sure this is possible. But basically, I am trying to see if I can bind a field in an entity to
Map<Skill,Set<Rating>> ratings;
CREATE TABLE Worker (
ID BIGINT PRIMARY KEY,
);
CREATE TABLE Skill (
ID BIGINT PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE (name)
);
CREATE TABLE WorkerSkillRating (
ID BIGINT PRIMARY KEY,
WorkerID BIGINT NOT NULL,
SkillID BIGINT NOT NULL,
Rating INT,
FOREIGN KEY (WorkerID) REFERENCES Worker (ID),
FOREIGN KEY (SkillID) REFERENCES Skill (ID),
FOREIGN KEY (Rating) REFERENCES Rating (ID)
);
CREATE TABLE Rating (
ID BIGINT PRIMARY KEY,
score TINYINT NOT NULL,
comments VARCHAR(256)
);
Entities
#Entity
public class Skill {
#Id
private Long id;
private String name;
public Skill(String name) {
this();
this.name = name;
}
public Skill() {
this.id = Math.abs( new Random().nextLong());
}
}
#Entity
public class Worker {
#Id
private Long id;
// The open question
public Map<Skill, Set<Rating>> ratings;
}
#Entity
public class Rating {
#Id
private Long id;
private Byte score;
private String comments;
}
According to the JSR-0038 the JPA spec. When using Map, the following combination are just allowed: Basic Type, Entities and Embeddables.
Map<Basic,Basic>
Map<Basic, Embeddable>
Map<Basic, Entity>
Map<Embeddable, Basic>
Map<Embeddable,Embeddable>
Map<Embeddable,Entity>
Map<Entity, Basic>
Map<Entity,Embeddable>
Map<Entity, Entity>
I don’t think there is pretty much deal to have a possible mapping in the way that you want but that is out of the specs and most of the providers follow them, I think that mapping is not very common at all.
"worker has many skills and he may have been given many ratings on a
single skill. "
Then add to the skill class a Set<Ratings>, instead of nested directly in the map as the value of it.
It might not answer your question with the map but...
It looks like your rating table is unnecessary.
You could instead have
CREATE TABLE Worker (
ID BIGINT PRIMARY KEY,
);
CREATE TABLE Skill (
ID BIGINT PRIMARY KEY,
name VARCHAR(32) NOT NULL,
UNIQUE (name)
);
CREATE TABLE WorkerSkill (
ID BIGINT PRIMARY KEY,
WorkerID BIGINT NOT NULL,
SkillID BIGINT NOT NULL,
score TINYINT NOT NULL,
comments VARCHAR(256)
FOREIGN KEY (WorkerID) REFERENCES Worker (ID),
FOREIGN KEY (SkillID) REFERENCES Skill (ID)
);
Note I moved the rating information to WorkerSkill table.
Then you can map your entities per below
#Entity
public class Skill {
#Id
private Long id;
private String name;
// Getter setters const etc
}
#Entity
public class WorkerSkill {
#Id
private Long id;
private int score;
private String comments;
#ManyToOne
private Skill skill;
#ManyToOne
private Worker worker;
// Getter setters const etc
}
#Entity
public class Worker {
#Id
private Long id;
#OneToMany
public List<WorkerSkill> workerSkills = new ArrayList<>();
// Getter setters const etc
}
Then you can access all worker's skill using worker.getWorkerSkill();
I have Eclipselink persistence provider tuned on DB2 DB. Where is 3 tables which simplified definition are listed below:
CREATE TABLE root
(
id CHAR(32) NOT NULL PRIMARY KEY,
rec_type VARCHAR(20)
);
CREATE TABLE derived
(
id CHAR(32) NOT NULL PRIMARY KEY,
...
);
ALTER TABLE derived ADD CONSTRAINT fk_derived_to_root FOREIGN KEY (id) REFERENCES root(id);
CREATE TABLE secondary
(
derived_id NOT NULL PRIMARY KEY,
...
);
ALTER TABLE secondary ADD CONSTRAINT fk_secondary_to_derived FOREIGN KEY (derived_id) REFERENCES derived(id);
Java entity classes for these entities are listed below,
RootEntity:
#javax.persistence.Table(name = "ROOT")
#Entity
#DiscriminatorColumn(name = "REC_TYPE")
#Inheritance(strategy = InheritanceType.JOINED)
public class RootEntity {
private String id;
#javax.persistence.Column(name = "ID")
#Id
#GeneratedValue(generator = "system-uuid")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
private String principalType;
#Column(name = "PRINCIPAL_TYPE")
public String getPrincipalType() {
return principalType;
}
public void setPrincipalType(String principalType) {
this.principalType = principalType;
}
...
}
DerivedEntity:
#javax.persistence.Table(name = "DERIVED")
#Entity
#DescriminatorValue("DERIVED")
public class DerivedEntity extends RootEntity {
private SecondaryEntity secondaryEntity;
#OneToOne(mappedBy = "derived_id")
public SecondaryEntity getSecondaryEntity() {
return secondaryEntity;
}
public void setSecondaryEntity(SecondaryEntity secondaryEntity) {
this.secondaryEntity = secondaryEntity;
}
...
}
I see no derived table insertion in the test logs:
--INSERT INTO ROOT (ID, REC_TYPE) VALUES (?, ?)
bind => [241153d01c204ed79109ce658c066f4c, Derived]
--INSERT INTO SECONDARY (DERIVED_ID, ...) VALUES (?, ...)
bind => [241153d01c204ed79109ce658c066f4c, ...]
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.ibm.db2.jcc.am.fo: DB2 SQL Error: SQLCODE=-530, SQLSTATE=23503, SQLERRMC=SCHEM.SECONDARY.FK_SECONDARY_TO_DERIVED, DRIVER=3.57.82
So question is: why Eclipselink don't insert new record into DERIVED table prior to insertion to SECONDARY table?
P.S. Everything is working fine when no SECONDARY table (ROOT and DERIVED tables only) or no inheritance used (DERIVED tables generates id).
For inheritance JPA assumes the foreign key constraints in related table refer to the root table.
You can change your constraint to refer to the root table, or,
use a DescriptorCustomizer to set,
descriptor.setHasMultipleTableConstraintDependecy(true);
or,
customizer the OneToOneMapping to have its foreign key refer to the secondary table (JPA annotation always make it refer to the root table).
Please log a bug though, as JPA join columns should allow you to define a foreign key to the secondary table.
The reason that EclipseLink does defer the insert into the secondary table is to allow inserts to be grouped by tables to allow batch writing and avoid database deadlocks.