Hibernate mapping: one column to multiple tables - java

I have a 'best practice' question for a scenario.
Scenario:
Multiple entities in a DB, for example, Document, BlogPost, Wiki can be shared by individuals. Instead of creating a share table for each entity, a single Share table is created. The issue is, how to map the share table with different entities?
I have three options, please advise which option is best, and if there is a better option.
Option1:
Create table Shares as:
SHARES
id (unique)
entityId (non DB enforced FK to DOCUMENTS, WIKIS, POSTS etc.)
entityType
sharedBy
sharedWith
sharedDate
Here, entityId will be a FK to documentId, wikiId, postId etc. etc. and entityType will identity what type the entityId is.
This has issues in Hibernate modelling, when creating Share to entity mapping, such as share.getDocument() or share.getWiki() etc.
Option 2:
Create table Shares which only holds share information, and then create resolution tables that tie the share to the entity.
SHARES
id(PK)
sharedBy
sharedWith
sharedDate
shareType (helper field for searches)
SHARES_DOCUMENTS
share_id (unique ID and FK, one to one with SHARES)
document_id (FK to DOCUMENTS)
SHARES_POST
share_id (unique ID and FK, one to one with SHARES)
post_id (FK to POSTS)
more share tables here.
So, hibernate wise, Share can have one to one for each of the share types (like share.getDocument(), share.getPost(), and shareType will identify which relationship is 'active' )
Option 3
Similar to option 1, but create individual columns instead of entity id
SHARES
id (unique ID)
documentId (FK to DOCUMENTS, nullable)
postId (FK to POSTS, nullable)
wikiId (FK to WIKIS, nullable)
sharedBy
sharedWith
sharedDate
sharedType
Here, each column could be mapped to respective entity, but they are nullable. sharedType can identify which relationship is 'active'.
So, the question is , which practice is best, both database wise as well as hibernate mapping (and eventual querying, performance wise).
Thanks
M. Rather

As suggested by TheStijn, after looking into different ways to setup inheritance relationships, I went with 'Single Table per class hierarchy' approach, and ended up with the table like:
SHARES
---------
id PK
shared_by FK to User
shared_with FK to User
shared_Date
document_id nullable FK to Document
post_id nullable FK to Posts
... more ids here to link to more entities
type_discriminator (values, DOCUMENT, POST ... )
On Hibernate/Java side,
One Share abstract class as...
#Entity
#Table(name="SHARES")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE_DISCRIMINATOR", discriminatorType=DiscriminatorType.STRING)
public abstract class Share {
#Id
#Column( name="ID", nullable=false )
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#ManyToOne
#JoinColumn( name="SHARED_BY", nullable=false )
private User sharedBy;
#ManyToOne
#JoinColumn( name="SHARED_WITH", nullable=false )
private User sharedWith;
#Column(name="SHARED_DATE", columnDefinition="TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP", nullable=false)
#Temporal(TemporalType.TIMESTAMP)
private Date sharedDate;
...
}
And two normal classes..
#Entity
#DiscriminatorValue("DOCUMENT")
public class SharedDocument extends Share {
#ManyToOne
#JoinColumn( name="DOCUMENT_ID", nullable=true )
private Document document;
....
}
#Entity
#DiscriminatorValue("POST")
public class SharedPost extends Share {
#ManyToOne
#JoinColumn( name="POST_ID", nullable=true )
private Post post;
....
}
As for usage, use the concrete classes only as:
#Test
public void saveNewDocumentShare(){
SharedDocument sharedDocument = new SharedDocument();
sharedDocument.setDocument(document1);
sharedDocument.setSharedBy(teacher1);
sharedDocument.setSharedWith(teacher2);
sharedDocument.setSharedDate(new Date());
sharedDocument.setCreatedBy("1");
sharedDocument.setCreatedDate(new Date());
sharedDocument.setModifiedBy("1");
sharedDocument.setModifiedDate(new Date());
SharedDocument savedSharedDocument = dao.saveSharedDocument(sharedDocument);
assertNotNull(savedSharedDocument);
assertThat(savedSharedDocument.getId(),notNullValue());
}
#Test
public void saveNewPostShare(){
SharedPost sharedWikiPage = new SharedWikiPage();
sharedPost.setPost(post1);
sharedPost.setSharedBy(teacher1);
sharedPost.setSharedWith(teacher2);
sharedPost.setSharedDate(new Date());
sharedPost.setCreatedBy("1");
sharedPost.setCreatedDate(new Date());
sharedPost.setModifiedBy("1");
sharedPost.setModifiedDate(new Date());
SharedPost savedSharedPost = dao.saveSharedPost(sharedPost);
assertNotNull(savedSharedPost);
assertThat(savedSharedPost.getId(),notNullValue());
}

This is clearly a many-to-many relationship.
Default scenario for mapping those type of things is to use a separate table for connection information.
Something like:
table shared_connections {
number owner_id
,number shared_id
}
All objects that are shareable should extend some basic class ex: AbstractSharedObject. (use #MappedSuperclass annotation and care about #Inheritance strategy).
and inside Individual class :
private Collection<AbstractSharedObject> shares;
map this collection as ManyToMany relationship.
P.S. For this to work you will need to guarantee that the ids of all shareable objects are unique.

Related

Why isn't the foreign key field of a Many-to-One relationship being set on insert?

My Spring web application allows users to update "Employee" records to change the fields or add new "Phone" records related to this "Employee" record. However, when the "Employee" record is submitted for update after adding a new "Phone" record, it's throwing a SQL error exception.
The problem is that the "employee_id" foreign key on "Phone" table to the "Employee" table isn't set in the eventual SQL insert statement submitted to the database. However, in the "PhoneEntity" JPA entity object that is referenced by the updated/merged "EmployeeEntity" object, the property associated with the employee_id database field isn't null, it's set to the "EmployeeEnity" Object being updated/merged.
From my understanding of JPA, having the entity property associated with a database field should set it when the insert statement for the entity's record is submitted to the database, but in this case it isn't which is causing this error.
I've tried stepping through with a debugger, and I have verified that the created PhoneEntity object is a member of EmployeeEntity's phones property, and that the same PhoneEntity's employee property is set to the same EmployeeEntity object (with the same object IDs) in a bidirectional relationship.
I've also set the hibernate.show_sql=true to see the SQL statement being submitted to the database and it includes the statement (with the ellipses being more fields):
Hibernate:
insert
into
phone
(id, employee_id, ...)
values
(?, ?, ...)
Which means that it is inserting a new phone for the new PhoneEntity object.
After trying to running this insert statement it gives the SQL error "Column 'employee_id' cannot be null". However like I said before, I've checked with the debugger and the employee property is indeed set to the EmployeeEntity object.
this is a simplified example of what my code looks like:
#Entity
#Table(name = "employee")
public class EmployeeEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST})
private Set<PhoneEntity> phones = new HashSet<>();
...
}
#Entity
#Table(name = "phone")
public class PhoneEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne
#JoinColumn(name = "employee_id", nullable = false)
private EmployeeEntity employee;
...
}
With tables that have the structure created by the following SQL statements.
CREATE TABLE employee (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);
CREATE TABLE phone (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
...
FOREIGN KEY(employee_id) REFERENCES employee(id)
);
And the following is where it actually submits the updates to the entity manager to make updates to the database.
public void update(EmployeeDomain employee) {
EmployeeEntity entity = employeeDomainToEntity.transform(employee)
getEntityManager().merge(entity);
}
The EmployeeEntity and PhoneEntity objects are created by converting similar domain objects that were in turn deserialized from a http request. I'd include more of this section of the code but, as I've mentioned, I've already confirmed with my debugger that the actual entity objects being submitted to the merge are already in the form that we expected with the phones fields and employee fields being set correctly, so the end entities should be correct.
In the official JPA specification document (version 2.1) in section "3.2.7.1 Merging Detached Entity State" (page 85) we find:
For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
This explains that you are lacking cascade=MERGE for the annotation of the phones field.
As proposed in thanh ngo's answer, the aforementioned definition (or: explanation) thus translates to:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();
Alternatively, you could also make use of cascade=CascadeType.ALL. However, this would also include operations such as CascadeType.REMOVE which might not always be intended.
Hope it helps.
I think the problem is that you are using merge.
The cascade type setting for the entity should be:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();

Implementing hierarchical data structures with JPA (fixed depth)

I have a hierarchical data structure with a fixed depth of 4. For a better understanding, let's assume the following (just an example):
The "root" level is called countries
Each country contains an arbitrary amount of states
Each state countains an arbitrary amount of counties
Each county contains an arbitrary amount of cities
So there are always 1-N relationships between the levels.
A very important usecase (given the id of a country) is to load the whole "content" of a country at once with the smallest possible impact on the performance of the database.
In a first naive approach, I created 4 entitiy classes in Java where the entity "Country" contains a list of the type "State", the entity "State" contains a list of the type "County" and so on...
But what JPA creates afterwards are of course not 4 tables, but 7 (4 for the entities + 3 for the connection between the levels due to 1-N). I don't know if this is a good solution since there is a lot of joining going on under the hood.
I also tried to map the subtypes to their parent types (a city belongs to one county, a county belongs to one state, a state belongs to one country). This results in 4 tables, but makes it more difficult to retrieve all data at once from the application's point of view. If I'm not wrong, I would need 4 different requests instead of one.
How could I solve this problem? Is there a way to combine a simple table layout (with four tables, not seven) with easy to use entity classes (a parent type should know its children)?
If not, how would you realize this?
I'm using JPA with Hibernate and PostgreSQL.
You can avoid the 3 extra mapping tables by using the #JoinColumn annotation rather than the #JoinTable annotation that I suspect you are using.
So for example:
COUNTRY
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="country")
private List<State> stateList;
STATE
#ManyToOne
#JoinColumn(name="country_id")
private Country country
The database tables would be as follows:
Country
country_id => primary key
State
state_id => primary key
country_id => foreign key
This way the mapping tables between all the 4 entities can be avoided.
You can achieve this pretty easily using JPQL:
SELECT DISTINCT country
FROM Country country
JOIN FETCH country.states states
JOIN FETCH states.counties counties
JOIN FETCH counties.cities cities
WHERE country.id = :countryId
Using fetchType = FetchType.EAGER on #OneToMany/#ManyToOne(believe that one is already EAGER by default) will achieve similar results.
It's very simple use bidirectional mapping. Go through that link
How to delete Child or Parent objects from Relationship?
Make some changes like below
Country Entity:
------
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<States > states;
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Counties> counties;
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Cities> cities;
-------
setters & getters
States Entity:
-----
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
-----
Counties Entity:
--------
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
-------
Cities Entity:
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
---------
After compilation of all entity's do your insertion . Only 4 will create and read your data by using Country object id.
You already have the solution: four table is the way to go, with bidirectional relationships (use the mappedBy property in the not-owning side of every relationship). If the relationships are EAGER-fetched, than all entities are automatically loaded. If you want to use LAZY fetching, you could try a named query in order to load the entity with all relationships loaded:
SELECT DISTINCT c FROM Country c LEFT JOIN FETCH c.states s LEFT JOIN FETCH s.counties co...
Did you try to declare the fetch type of the relations explicitely to eager with your second approach (default is lazy, that's why you have to do four queries).
E.g.
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn ...
private ...;
see here: http://www.concretepage.com/hibernate/fetch_hibernate_annotation
Here is how your entities will look like:(You can use EAGER Loading instead of LAZY as well if you want)
Entity: Country
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="COUNTRY_ID")
private List<State> stateList;
Entity: State
This table has COUNTRY_ID that is Foreign Key to Country
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="STATE_ID")
private List<County> countyList;
#Column(name="COUNTRY_ID")
private Integer countryId;
Entity: County
This table has STATE_ID that is Foreign Key to State
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="COUNTY_ID")
private List<City> cityList;
#Column(name="STATE_ID")
private Integer stateId;
Entity: City
This table has COUNTY_ID that is Foreign Key to County
#Id
private Integer id;
#Column(name="COUNTY_ID")
private Integer countyId;
Your JPQL will be:
Select o from Country o where o.id=10
This will pick The Country Entity along with all the mappings like below.
Country
Holding List of States
Each States Holding List of Counties
Each Counties Holding LIst of Cities
For a requirement like yours, I would suggest to have a tree-like structure to maintain the hierarchical location data. It is relatively easy to implement & maintain and is more scalable & extensible.
In order to implement tree you need to have 2 tables LOCATION_NODE (Location ID, Location Name, Location Type[country, state, county, city]) & LOCATION_REL (Relation ID, Parent ID, Child ID). Below is the basic implementation of the tree idea.
public class LocationRel<T> {
private LocationNode<T> root;
public LocationRel(T rootData) {
root = new LocationNode<T>();
root.data = rootData;
root.children = new ArrayList<LocationNode<T>>();
}
public static class LocationNode<T> {
private T data;
private LocationNode<T> parent;
private List<LocationNode<T>> children;
}
}
This is the basic building block for a tree. You may need to add methods for add to, removing from, traversing, and constructors. But, once implemented, you have the freedom to add any new location type, change the hierarchy, add node, delete node etc with your hierarchical data.
Think out of the box.
Shishir
If you need the performance, I would suggest to de-normalize your tables and create 4 entities with following attributes (columns):
Country: id, name
State: id, countryId, name
County: id, countryId, stateId, name
City: id, countryId, stateId, countyId, name
(mapping is obvious)
Then you will be able to build a simple SQL queries.
If you need performance, prefer named queries as they are compiled at initialization time.
E.g. select all cities by country: "SELECT id, name FROM city WHERE country_id=?"
You may even not declare a references between entities using #ManyToOne, but just declare a simple #Columns. API call will, most likely, accept IDs (countryId, stateId), so you'll be better to pass that IDs as parameters to DAO. Most likely, you have a locations tables filled in once by sql script and the data should not be modified. Create foreign keys to guarantee data integrity.
And do you really need a tree-like structure in memory? If so, create it by hand, it is not very complex.
Searching Online, I found a couple of Links on JPQL which I think might help.
Link 1
Link 2
Anyways,
JPQL is one of the best ways to achieve this, try out this Query
SELECT DISTINCT country FROM Country country JOIN FETCH country.states states JOIN FETCH states.counties counties JOIN FETCH counties.cities cities WHERE country.id = :countryId
A solution that is useful, if you have relations that point to their parent only is the following:
With records:
#Entity
public class Country
{
#Id
private Long id;
}
#Entity
public class State
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "country_id", referencedColumnName = "id", nullable = false)
Country country;
}
#Entity
public class County
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "state_id", referencedColumnName = "id", nullable = false)
State state;
}
#Entity
public class City
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "county_id", referencedColumnName = "id", nullable = false)
County county;
}
You can get all cities of a country with:
public interface CityRepository extends JpaRepository<City, Long>
{
List<City> findByCounty(County county); // county is a direct field of City
#Query("SELECT c FROM City c WHERE c.county.state.country = ?1")
List<City> findByCountry(Country country);
}

Hibernate Exception: Missing Column (column exists)

Okay, so within the database we have a table called distributionCompanies, created like so:
CREATE TABLE `distributionCompanies` (
`distributionCompanyID` INT(11) NOT NULL,
`distributionCompanyName` VARCHAR(255) NOT NULL,
PRIMARY KEY (distributionCompanyID)
);
I'm trying to map this table to a class using Hibernate:
#Entity
#Table(name = "distributionCompanies")
public class DistributionCompany implements DatabaseObject {
#Id
#GeneratedValue
#Column(name = "distributionCompanyID", length = 11, unique = true, nullable = false)
private int distributionCompanyID;
....
However, when running, I hit this issue:
Initial SessionFactory creation failedorg.hibernate.HibernateException: Missing column: distributionCompanyID_distributionCompanyID in database2.distributionCompanies
This isn't the only table in the database, and I've managed to map other classes successfully using the same method, so I'm a little stumped as to why this is causing an issue.
Thank you for your time,
Samuel Smith
EDIT: In response to Xavi's comment, I temporarily removed another mapping for the column, and the error went away, so the bad-egg probably lays in the following code:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinTable(name = "distributionCompanies", joinColumns = { #JoinColumn(name = "distributionCompanyID", nullable = false) })
private int distributionCompanyID;
Hibernate is looking for a column named distributionCompanyID_distributionCompanyID in your distributionCompanies table.
This is probably due to a ToOne association mapping towards this table without #JoinColum.
From Hibernate Documentation:
The #JoinColumn attribute is optional, the default value(s) is like in one to one, the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column in the owned side. In this example company_id because the property name is company and the column id of Company is id.
If you've got a #ManyToOne or #OneToOne association mapping in another entity, this would explain why Hibernate is looking for such a column.
EDIT Seeing the association mapping you posted, it looks like it should be:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinColumn(name = "distributionCompanyID")
private DistributionCompany distributionCompany;
The #JoinTable annotation is used to specify a join table (that means an intermediate table used to model many-to-many associations). And the point of mapping an association would be to dispose of the mapped object instance (in this case a DistributionCompany, not just a distributionCompanyId).

hibernate collection mapping - how can I put the collection owner id as foreign key into a collection's element's table?

I have a class Product and a class Part, where every part can only belong to one one product. Each product has a list of its parts, but a part has no reference to its product.
#Entity
#Table (name= "products")
class Product {
#Id
#GeneratedValue
#Column(name = "Id")
int id;
#Column(name = "Name")
String name;
#???
List<Part> myParts;
parts:
#Entity
#Table (name= "parts")
class Part {
#Id
#GeneratedValue
#Column(name = "Id")
int id;
#Column(name = "Name")
String name;
}
In my database the table 'products' does not store information about its parts, but the 'parts' table keeps track of the products in a row 'product_id'.
products:
| id | name |
parts:
| id | name | procuct_id |
I think it is quite normal to have this contrary approaches of the OO- and the ORM "world", but I can't find out how to persist my objects with Hibernate to this structure!
For the one-to-many annotation I only found examples where the part-id would have been stored in the product table.
For many-to-one it seems as if I needed a reference to the product in my parts objects, isn't it?
I hope that I am wrong! ;)
Does anybody know if there is a way to map this without chaning my class or table structure?
( I would be very, very happy if you could explain it with annotations rather than with xml :) )
I think you mean a one-to-many unidirectional mapping, try something as follows:
#OneToMany
#JoinColumn(name="product_id")
List<Part> myParts;
You can see an example here, #OneToMany. Check out "Example 3" in that link.
Note: Since you have the product_id as foreign key in the parts table, it is advised to also have a Product type field in the Part class.

Hibernate: virtual column

I'm looking for a way to associate a entity with another entity purely through hibernate (no extra relation db column mappings) and not requiring separate DAO calls. I searched around for a solution and the only thing I could find was #Formula
but I could not get it to work. Consider:
#Entity
class CommonEntity {
#MagicAnnotation("maybe with HQL?")
private SuperEntity superEntity;
}
#Entity
class SuperEntity { }
What this means is that sometimes CommonEntity is a SuperEntity and I want to have a getter on the POJO itself so it has access the SuperEntity via a simple get(). Is there any way to do this cleanly so that when I do something like commonEntityDAO.get(1L); where 1L IS a SuperEntity, then the entity will be set?
The tables in the database would look like:
table common_entity ( common_entity_id int primary key, name string );
table super_entity ( super_entity_id int primary key, extra_field string, common_entity_id int );
Since you have an is a relationship, this is a primary candidate for inheritance. Depending on the database representation you like there are multiple inheritance strategies. check this
Can't you simply use #OneToOne? Your database schema looks suitable for it.
#Entity
class CommonEntity {
#OneToOne(mappedBy = "commonEntity")
private SuperEntity superEntity;
}
#Entity
class SuperEntity {
#OneToOne
#JoinColumn(name = "common_entity_id")
private CommonEntity commonEntity;
}
Note that in this case you need a bidirectional relationship with SuperEntity being an owning side, since it holds the foreign key.

Categories

Resources