Hibernate: Several tables of one entity -> inherit hibernate-mapping - java

I have an entity from which i want to have several tables in my sql database.
For Example, I have the Java Class in which i have an collection of another Java Class
#Entity
class SqlEntity{
#Id
#Column(unique = true)
private Date date = null;
#Column
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL, CascadeType.PERSIST,
CascadeType.MERGE }, mappedBy = "CollectionData")
private Collection<CollectionData> collectionData = new ArrayList<>();
#Column(name="columNameX")
int attributeX;
#Column(name="columNamey")
int attributeY;
...
}
And i want different Data in different Tables according to where the Data are from:
SQL_ENTITY_GERMANY,
SQL_ENTITY_USA,
SQL_ENTITY_UK,
...
I was able to accomplish this by writing an xml-mapping for every table(before that I had only java annotations to map the entity).
But I had to write an complete mapping for every table like this:
<hibernate-mapping>
<class name="...SqlEntity" table="SQL_ENTITY_GERMANY"
entity-name="SQL_ENTITY_GERMANY">
<id name="date" type="date" column="date">
</id>
<property name="columnNameX" column="attributeX" type="int" />
<property name="columnNameY" column="attributeY" type="int" />
...
<bag name="collectionData" cascade="all">
<key column="date" />
<one-to-many class="COLLECTION_DATA_GERMANY" />
</bag>
</class>
<class name="...collectionData" table="COLLECTION_DATA_GERMANY"
entity-name="COLLECTION_DATA_GERMANY">
<id name="id" type="long" column="id">
<generator class="native" />
</id>
<property name="columnNameX" column="attributeX" type="int" />
<property name="columnNameY" column="attributeY" type="int" />
...
<many-to-one name="collectionData" class="SQL_ENTITY_GERMANY"
fetch="select">
<column name="date" not-null="true" />
</many-to-one>
</class>
</hibernate-mapping>
So if I want to change anything in one entity class (like adding members), i have to change it in every single xml-mapping, too.
So i thought it maybe possible to just inheritate the mapping, so that the annotations of the class are still guilty as long as i dont overwrite it.
I tried to google and search on stackoverflow on this topic, but i found only things about java classes inheritance.
Or is there another solution which could help me with this?

Since your database is non-normalized, the only way of achieving what you want is creating an abstract class and several concrete subclasses with appropriate country codes.
If you are able to alter a schema, I'd suggest adding a column COUNTRY_CODE VARCHAR(2) NULL to your SQL_ENTITY table, insert all data from your SQL_ENTITY_* tables.
You can do so by:
1)Creating a new SQL_ENTITY table
CREATE TABLE NEW_SQL_ENTITY (
id_entity INTEGER NOT NULL AUTO_INCREMENT;
value1 VARCHAR(100) NOT NULL;
value2 VARCHAR(100) NOT NULL;
country_code VARCHAR(2) NULL;
)
2) Inserting the data from the other tables:
INSERT INTO NEW_SQL_ENTITY (value1,value2,country_code) VALUES
(SELECT value1,value2, 'UK' from SQL_ENTITY_UK)
3) Repeating for all SQL_ENTITY tables

Related

How to insert constant value in a join table with Hibernate in a many-to-many relation?

I have a mapping like A - AB - B but the AB table is also a join table for other tables (yeah, thanks, no comment :p). In the AB table, I have a CODE (non-null) column.
I can fetch the correct datas by adding a where tag in the mapping, but when I insert values, Hibernate does not add the value in the CODE column... Well, I don't know how to tell Hibernate to do that.
Here is the mapping of the A table to get a Set:
<set name="b" table="AB" lazy="false" where="CODE='1.2.3'">
<key column="A_ID" />
<many-to-many column="B_ID" class="B">
</many-to-many>
</set>
Hibernate create an insert with only the A_ID and the B_ID values, I'd like to tell Hibernate to insert CODE='1.2.3' in its SQL INSERT query.
Many thanks for your help,
UPDATE
The idea in a Java point of view is to have a signature like this in A: getB():Set<B> I do not want getAB():Set<AB>.
Thanks
for AB use two classes one for IDs ABIdClass(A_ID,B_ID) and the other for Code(and all extra columns) ABClass(CODE) and use default attribute of column for constant value:
<class name="ABClass" table="AB">
<composite-id name="id" class="ABIdClass">
<key-many-to-one name="a" class="A" column="a_id" />
<key-many-to-one name="b" class="B" column="b_id" />
</composite-id>
<property name="code" type="string">
<column name="code" not-null="false" default="1.2.3" />
<property/>
</class>
you can also try something like this:
ABIdClass abId = new ABIdClass();
abId.setA(a);
abId.setB(b);
ABClass ab = new ABClass();
ab.setId(abId);
ab.setCode("1.2.3");
a.getABClass().add(ab);
hope these be useful.
You should be able to achieve what you are looking for using the Hibernate Table per Class Hierarchy approach.
abstract class Mapping { private String code; }
class TableA {}
class TableB {}
class TableAB extends Mapping { private TableA a; private TableB b; }
class TableC {}
class TableD {}
class TableCD extends Mapping { private TableC c; private TableD d; }
class TableE {}
class TableF {}
class TableEF extends Mapping { private TableE e; private TableF f; }
<class name="TableA" table="tableA"/>
<class name="TableB" table="tableB"/>
<class name="TableC" table="tableC"/>
<class name="TableD" table="tableD"/>
<class name="TableE" table="tableE"/>
<class name="TableF" table="tableF"/>
<class name="Mapping" table="mapping">
<discriminator column="CODE" type="string"/>
<subclass name="TableAB" discriminator-value="1.2.3">
<property name="a" column="a_id"/>
<property name="b" column="b_id"/>
</subclass>
<subclass name="TableCD" discriminator-value="4.5.6">
<property name="c" column="a_id"/>
<property name="d" column="b_id"/>
</subclass>
<subclass name="TableEF" discriminator-value="7.8.9">
<property name="e" column="a_id"/>
<property name="f" column="b_id"/>
</subclass>
</class>
Note that the columns a_id and b_id are common for all relationships. This will ensure that a single mapping table can be used for different relationships as you want.

Hibernate, mapping inherited fields

Lets say I have a following class hierarchy:
class Person {
String name;
int age;
}
class DatabasePerson extends Person {
int databaseId;
}
Now I would like to map the DatabasePerson, please notice that I don't would like to tell hibernate that there exist a Person class, hibernate should only know about the DatabasePerson class while xml or annotation mapping. Is it possible to map the age and name fields with hibernate adopting the above mentioned condition ? In other words I would like to map the DatabasePerson class with hibernate and hibernate should not know about the Person class. In xml I would make it like this (Pseudo-code):
<hibernate class="DatabasePerson">
<field name="id"/>
<field name="name"/>
<field name="age"/>
</hibernate>
The reason for doing that is to keep the single responsibility principle. I don't would like to put the databaseId field into the Person class, because the person class shouldn't know that it is persisted. I don't would like to include any hibernate annotations into Person class because I have there pure entity logic and I don't want do import there any database related stuff like hibernate. All mappings I would like to do in the DatabasePerson class, this is the place where I would like to put the additional databaseId field, write hibernate annotations (or maybe xml, I don't know it right now, I would like to postpone this decision). I other words we want to keep the database related stuff and our application logic in separate classes.
Edit:
Can I use something like this?:
<class name="Person" table="PERSON" discriminator-value="P">
<discriminator column="DISCRIMINATOR" type="string" />
<property name="name" />
<property name="age" />
<subclass name="DatabasePerson" extends="Person" >
<id name="databaseId" column="ID">
<generator class="native" />
</id>
</subclass>
</class>
Notice that the database id is in the DatabasePerson scope in this xml.
Edit:
Is this xml mapping respective to the annotation 'mapped superclass' ? I think I preffer to use xml instead of annotations so my question is how to use #MappedSuperclass in xml:
<class name="Person" table="PERSON" discriminator-value="P">
<id name="databaseId" column="PERSON_ID">
<generator class="native" />
</id>
<discriminator column="DISCRIMINATOR" type="string" />
<subclass name="DatabasePerson" extends="Person" discriminator-value="E">
<property name="name" column="name" />
<property name="age" type="int" column="age" />
</subclass>
</class>
I think can you use MappedSupperclass annotation or singel table inheritance strategy for use only one table in database. Both case the JPA/Hibernate know about Person class, but It doesn't create table for this class. (Of course could you use XML adjustment instead of annotations, but I've used only annotations.)
#MappedSuperclass annotation.
If you want to use only one database table (DatabasePerson) which included all columns from both java classes, than you could use following annotations:
#MappedSuperclass
class Person {
String name;
int age;
}
#Entity
class DatabasePerson extends Person {
int databaseId;
}
I think in this case is better change Person class to abstract class.
InheritanceType.SINGLE_TABLE
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name="yourtype",
discriminatorType=DiscriminatorType.STRING
)
class DatabasePerson{
int databaseId;
}
#Entity
#DiscriminatorValue("P")
class Person extends DatabasePerson{
String name;
int age;
}
This startegy use discriminator column and persist only one table.
Try it
<hibernate-mapping package="Yourpackage">
<class name="Person" table="PERSON" >
<id name="databaseId" column="ID">
<generator class="native" />
</id>
<discriminator column="DISCRIMINATOR" type="string" />
<property name="name" />
<property name="age" />
<subclass name="DatabasePerson" extends="Person" >
<property name="databaseId" column="database_Id" />
</subclass>
</class>
</hibernate-mapping>
May be possible same example here
http://viralpatel.net/blogs/hibernate-inheritence-table-per-hierarchy-mapping/
Hibernate XML configuration deals perfectly with it and I've used it extensively in one of the projects I worked on. My scenario was the following, which might sound different but it is just the same as yours.
class Entity {
private int _id;
public int getId() { return _id; }
public void setId(int id) { _id = id; }
}
class User extends Entity {
private String _email;
public String getEmail() { return _email; }
public void setEmail(String email) { _email = email; }
}
My intention here was to have User (and all my others entities) to inherit the id, as you're trying to have DatabasePerson inheriting Person.name and age.
Then, when configuring the mapping for User I've written the following XML file.
<hibernate-mapping>
<class name="User" table="User">
<id name="id" type="int" column="id">
<generator class="identity"/>
</id>
<property name="email" type="string">
<column name="email" sql-type="varchar(255)"/>
</property>
.....
</class>
</hibernate-mapping>
As you can see, there is no any single reference to Entity from the above XML file, which in your case translates to: your XML file only deals with DatabasePerson as Person wouldn't exist at all; Hibernate will use Java introspection to retrieve id from Entity, as Person.name for your case.
The only thing I'm not sure, is whether your Person.name and Person.age field can be left as protected or should be promoted to public. Either way, you can leave them as protected and (eventually) provide the method DatabasePerson.getName() and DatabasePerson.setName() (no need to provide these getter/setter at Person level).
As a side note, in other answers you have been suggested to use hibernate "subclass" or "MappedSuperclass". These are not required, and they usage is even semantically wrong in your case: They are supposed to be used when you have a DB entity extending another DB entity, but in your case DatabasePerson is a DB Entity extending a POJO class (Person).

Hibernate: Inconsistent primary key generation due to one-to-one relation

I have two one-to-one relations here between a class called "MailAccount" and the classes "IncomingServer" and "OutgoingServer".
(It's a Java application running on Tomcat and Ubuntu server edition).
The mapping looks like this:
MailAccount.hbm.xml
<hibernate-mapping package="com.mail.account">
<class name="MailAccount" table="MAILACCOUNTS" dynamic-update="true">
<id name="id" column="MAIL_ACCOUNT_ID">
<generator class="native" />
</id>
<one-to-one name="incomingServer" cascade="all-delete-orphan">
</one-to-one>
<one-to-one name="outgoingServer" cascade="all-delete-orphan">
</one-to-one>
</class>
</hibernate-mapping>
IncomingMailServer.hbm.xml
<hibernate-mapping>
<class name="com.IncomingMailServer" table="MAILSERVER_INCOMING" abstract="true">
<id name="id" type="long" access="field">
<column name="MAIL_SERVER_ID" />
<generator class="native" />
</id>
<discriminator column="SERVER_TYPE" type="string"/>
<many-to-one name="mailAccount" column="MAIL_ACCOUNT_ID" not-null="true" unique="true" />
<subclass name="com.ImapServer" extends="com.IncomingMailServer" discriminator-value="IMAP_SERVER" />
<subclass name="com.Pop3Server" extends="com.IncomingMailServer" discriminator-value="POP3_SERVER" />
</class>
</hibernate-mapping>
OutgoingMailServer.hbm.xml
<hibernate-mapping>
<class name="com.OutgoingMailServer" table="MAILSERVER_OUTGOING" abstract="true">
<id name="id" type="long" access="field">
<column name="MAIL_SERVER_ID" />
<generator class="native" />
</id>
<discriminator column="SERVER_TYPE" type="string"/>
<many-to-one name="mailAccount" column="MAIL_ACCOUNT_ID" not-null="true" unique="true" />
<subclass name="com.SmtpServer" extends="com.OutgoingMailServer" discriminator-value="SMTP_SERVER" />
</class>
</hibernate-mapping>
The class hierarchy looks like this:
public class MailAccount{
IncomingMailServer incomingServer;
OutgoingMailServer outgoingServer;
}
public class MailServer{
HostAddress hostAddress;
Port port;
}
public class IncomingMailServer extends MailServer{
// ...
}
public class OutgoingMailServer extends MailServer{
// ...
}
public class ImapServer extends IncomingMailServer{
// ...
}
public class Pop3Server extends IncomingMailServer{
// ...
}
public class SmtpServer extends OutgoingMailServer{
// ...
}
Now, here comes the problem:
Although most of the time my application runs well, there seems to be one situation in which email servers get deleted, but the corresponding account doesn't and that's when this call is made:
session.delete(mailAccountInstance);
In a one-to-one relation in Hibernate, the primary keys between mail account and its servers must be equal, if not, the relation completely gets out of sync:
Example:
Imagine, the tables are filled with data like this:
Table "MailAccount" (Current auto_increment value: 2)
MAIL_ACCOUNT_ID NAME
0 Account1
1 Account2
Table "IncomingMailServer" (Current auto_increment value: 2)
MAIL_SERVER_ID MAIL_ACCOUNT_ID
0 0
1 1
Now, image the account with ID=1 gets deleted and new accounts get added. The following then SOMETIMES happens:
Table "MailAccount" (Current auto_increment value: 3)
MAIL_ACCOUNT_ID NAME
0 Account1
1 Account2
2 Account3
Table "IncomingMailServer" (Current auto_increment value: 2)
MAIL_SERVER_ID MAIL_ACCOUNT_ID
0 0
1 2
This completely messes up my database consistency.
How can I avoid this?
If you want a shared primary key, you can use the native id generator only once. You create the mail account first, which will generate its own id, but when you create the Incoming- or OutgoingMailServer, these need to take their id from the mailAccount property.
So you need the "foreign" generator:
<class name="OutgoingMailServer">
<id name="id" column="MAIL_SERVER_ID">
<generator class="foreign">
<param name="property">mailAccount</param>
</generator>
</id>
<one-to-one name="mailAccount" not-null="true" constrained="true"/>
<class>
You don't need a MAIL_ACCOUNT_ID column, since it will always be identical to the MAIL_SERVER_ID anyway.
Quite basic follow the reference about bidirectional one-to-one association on a primary key.

How to Stop Hibernate From Trying to Update one-to-many Over Join Table

Ok so I'm having bit of a problem with my Hibernate mappings and getting the desired behavior.
Basically what I have is the following Hibernate mapping:
<hibernate-mapping>
<class name="com.package.Person" table="PERSON" schema="MYSCHEMA" lazy="false">
<id name="personId" column="PERSON_ID" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">PERSON_ID_SEQ</param>
</generator>
</id>
<property name="firstName" type="string" column="FIRST_NAME">
<property name="lastName" type="string" column="LAST_NAME">
<property name="age" type="int" column="AGE">
<set name="skills" table="PERSON_SKILL" cascade="all-delete-orphan">
<key>
<column name="PERSON_ID" precision="12" scale="0" not-null="true"/>
</key>
<many-to-many column="SKILL_ID" unique="true" class="com.package.Skill"/>
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="com.package.Skill" table="SKILL" schema="MYSCHEMA">
<id name="skillId" column="SKILL_ID" type="java.lang.Long">
<generator class="sequence">
<param name="sequence">SKILL_ID_SEQ</param>
</generator>
</id>
<property name="description" type="string" column="DESCRIPTION">
</class>
</hibernate-mapping>
So lets assume that I have already populated the Skill table with some skills in it. Now when I create a new Person I want to associate them with a set of skills that already exist in the skill table by just setting the ID of the skill. For example:
Person p = new Person();
p.setFirstName("John");
p.setLastName("Doe");
p.setAge(55);
//Skill with id=2 is already in the skill table
Skill s = new Skill()
s.setSkillId(2L);
p.setSkills(new HashSet<Skill>(Arrays.asList(s)));
PersonDao.saveOrUpdate(p);
If I try to do that however I get an error saying:
WARN (org.slf4j.impl.JCLLoggerAdapter:357) - SQL Error: 1407, SQLState: 72000
ERROR (org.slf4j.impl.JCLLoggerAdapter:454) - ORA-01407: cannot update ("MYSCHEMA"."SKILL"."DESCRIPTION") to NULL
ERROR (org.slf4j.impl.JCLLoggerAdapter:532) - Could not synchronize database state with session
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
The reason I am getting this error I think is because Hibernate sees that the Skill with Id 2 has 'updated' its description to null (since I never set it) and tries to update it. But I don't want Hibernate to update this. What I want it to do is insert the new Person p and insert a record into the join table, PERSON_SKILL, that matches p with the skill in the SKILL table with id=2 without touching the SKILL table.
Is there anyway to achieve this behavior?
Instead of creating the Skill object yourself:
//Skill with id=2 is already in the skill table
Skill s = new Skill()
s.setSkillId(2L);
p.setSkills(new HashSet<Skill>(Arrays.asList(s)));
You should be retrieving it from the Hibernate Session:
Skill s = (Skill) session.get(Skill.class, 2L);
p.setSkills(new HashSet<Skill>(Arrays.asList(s)));
This way the Session thinks that the skill contained in p.skills is persistent, and not transient.
This may be possible if you don't cascade all-delete-orphan which is explicitely telling hibernate to cascade the changes.
But the right way would be IMO to load load the desired Skill entity from the database and to add it to the set of skills of the Person.

Legacy mapping with hibernate

For my current project I have to map a legacy database using hibernate, but I'm running into some problems.
The database is setup using one 'entity' table, which contains common properties for all domain objects. Properties include (among others) creation date, owner (user), and a primary key which is subsequently used in the tables for the domain objects.
A simple representation of the context is as such:
table entity
- int id
- varchar owner
table account
- int accountid (references entity.id)
table contact
- int contactid (references entity.id)
- int accountid (references account.accountid)
My problem exhibits itself when I try to add a collection mapping to my account mapping, containing all contacts belonging to the account. My attempts boil down to the following:
<hibernate-mapping>
<class name="Contact" table="entity">
<id name="id" column="id">
<generator class="native" />
</id>
<join table="contact">
<key column="contactid"/>
<!-- more stuff -->
</join>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="Account" table="entity">
<id name="id" column="id">
<generator class="native" />
</id>
<bag name="contacts" table="contact">
<key column="accountid" />
<one-to-many class="Contact"/>
</bag>
<join table="account">
<key column="accountid"/>
<!-- more stuff -->
</join>
</class>
</hibernate-mapping>
However, when I try to fetch the account I get an SQL error, stating that the entity table does not contain a column called accountid. I see why this is happening: the mapping tries to find the accountid column in the entity table, when I want it to look in the contact table. Am I missing something obvious here, or should I approach this problem from another direction?
This looks to me like you actually need to be mapping an inheritance, using the Table Per Subclass paradigm.
Something like this:
<class name="entity" table="entity">
<id name="id" column="id">
...
</id>
<joined-subclass name="contact" table="contact">
<key column="contactid"/>
</joined-subclass>
<joined-subclass name="account" table="account">
<key column="accountid"/>
</joined-subclass>
</class>
That's approximate by the way - it's described in detail in section 9.1.2 of the Hibernate documentation (just in case you can't find it, it's called "Table per subclass").
Cheers
Rich

Categories