Hibernate Docs Contradiction: SQL Normalization vs Entity Mapping on Multiple Tables - java

The Hibernate documentation states the following:
5.1.6.5. Mapping one entity to several tables
While not recommended for a fresh schema, some legacy databases force
your[sic] to map a single entity on several tables.
I have done a lot of reading on database normalization, and I don't really understand how you can both normalize a database and NOT map an entity on several database tables, unless you map several different entities and do a weird join statement manually. Or, you map 7 different entities and transfer them to one POJO. Is there somewhere that shows how to develop a "fresh schema" that is both normalized and takes a single entity to turn from database data to a java object?
Also, I understand that complete normalization isn't necessary in today's world of cheap storage, but I just found this statement to be at odds with everything else I've read. I am looking for a balance of normalized to de-normalized, but haven't found a simple way with the the Java Persistence API.
EDIT:
Example:
If I have a user Entity with the following:
#Entity
public class User {
long id;
String name;
String email;
int countryCode;
List<Images> uploadedImages;
}
I am not going to store the user's country name and collection of images on the same table, rather I will have the country on one table:
Table Countries Country_Code Country_Name
AF Afghanistan etc....
with country code and country name, then on , and uploaded images on a separate table with user id's,
Table UploadedImages User_Id Image_Name Image_Url
1 Hello.jpg Amazon S3
1 Goodbye.jpg Photobucket
So how do I do this with only one entity? Or do I get three separate entities from the database using information from the first entity? As you can tell, I'm a bit confused on the basic schema, How would I translate the data above to a java object?

Given your example
#Entity
public class User {
long id;
String name;
String email;
int countryCode;
List<Images> uploadedImages;
}
Here you have 3 entities - a User, a Country and an Image entity with each one probably mapping to 3 tables - one entity per table and a join table for the list of images.
So your User class then becomes
#Entity
public class User {
long id;
String name;
String email;
Country country
List<Images> uploadedImages;
}
#Entity
public class Country {
long id;
String name;
}
#Entity
public class Image{
long id;
String name;
String url;
}
And you'd add annotations to map the properties to the correct tables and columns.

What the documentation says has nothing to do with data normalization. This has to do with data partition/table optimization. So, if you have a table with a column that holds a big binary, you probably don't want that to be part of the same table as the main data. This was more common in the past, as data considered small by today's measures were considered "big" back then.
Normalization, on the other hand, is very welcome in OOP (and Hibernate). It's all about keeping things on their places, to minimize redundancy and dependency

Related

Mapping Multiple Tables into a Single Entity in JPA

I'm trying to learn JPA/Hibernate and I'm real green in this field. I tend to usually veer off and try things without knowing much about the API. So I decided to create a simple entity that retrieves information from multiple tables see this is easily implementable with JPA. The reason behind this is, if, hypothetically, the involving tables each has a few hundred columns and we only have a business need to retrieve a very few data, and we only need to focus on retrieval rather than inserts/updates/deletions, I would assume it is best to only retrieve the entire entity (specially if multiple rows need to be returned) then join them across other entities to derive a few columns.
I started up with two simple tables:
EMPLOYEES
EMPLOYEE_ID, EMAIL, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, etc...
DEPARTMENTS
DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, etc...
I want my entity to retrieve only the following columns solely based on EMPLOYEES.EMPLOYEE_ID:
EMPLOYEES.EMPLOYEE_ID
EMPLOYEES.MANAGER_ID
EMPLOYEES.DEPARTMENT_ID
DEPARTMENT.DEPARTMENT_NAME
One thing to notice here is that EMPLOYEES.MANAGER_ID is a self-referencing foreign key to EMPLOYEES.EMPLOYEE_ID.
I might create the following...
#SecondaryTable(name="DEPARTMENTS",
pkJoinColumns=#PrimaryKeyJoinColumn(name="managerId",referencedColumnName="employeeId")
)
#Table(name="EMPLOYEES")
#Entity
public class EmployeesDepartment {
#Id
private String employeeId;
private String managerId;
private String email;
private int departmentId;
#Column(name="DEPARTMENT_NAME",table="DEPARTMENTS")
private String departmentDesc;
// Setters and getters
}
Obviously this doesn't give us the correct answer due to the fact that the join between the secondary table (DEPARTMENTS) occurs between its MANAGER_ID and EMPLOYEES.EMPLOYEE_ID, rather than DEPARTMENTS.MANAGER_ID = EMPLOYEES.MANAGER_ID.
I cannot replace referencedColumnName="employeeId" with referencedColumnName="managerId" as managerId of #Entity EmployeesDepartment is not a primary key of EMPLOYEES.
And I can't do the following:
#OneToOne
#JoinColumn(name="managerId",table="DEPARTMENTS",referencedColumnName="employeeId")
private String managerId;
My question is, how can I make my join to be on DEPARTMENTS.MANAGER_ID = EMPLOYEES.MANAGER_ID while the WHERE clause of the query is based on EMPLOYEES.EMPLOYEE.ID? In other word, how can I have the entity that is mapped to the following query:
SELECT
E.EMPLOYEE_ID,
E.MANAGER_ID,
E.DEPARTMENT_ID,
D.DEPARTMENT_NAME
FROM EMPLOYEES E LEFT OUTER JOIN DEPARTMENTS D ON E.MANAGER_ID = D.MANAGER_ID
WHERE E.EMPLOYEE_ID = ?
Or are there better solution with less side effects, e.g. order of updates of tables, loading, etc.?

Play Framework 2.2 - Ebean - ManyToMany OrderBy relationship creation date

I have 2 models, a workout and an exercise as follows:
#Entity
public class Workout extends Model {
#Id
public Integer id;
#ManyToMany(cascade = CascadeType.REMOVE)
public List<Exercises> exercises;
}
#Entity
public class Exercise extends Model {
#Id
public Integer id;
#ManyToMany
public Workout workout;
}
When I load a workout and attempt to display it, I want the exercises to be displayed in the order that the relationship (between the workout and exercise) was created. However, the exercises are instead displayed in the order that the exercises were created. Here's a sample of the display (in case it helps):
<ul>
#workout.exercises.map { exercise =>
<li>
#exercise.name
</li>
}
</ul>
Any ideas on how I can achieve this? I've tried adding #OrderBy to the property definition, but this doesn't allow me to order by the relationship table fields, which would be ideal (with the addition of a created_date field on that table, as well).
Much appreciated!
There is no easy way as far as I can tell. Just create a getSortedExcercises() which returns the list sorted to use in the templates:
public List<Exercises> getSortedExcercises() {
List<Exercises> l = new ArrayList(this. exercises);
Collections.sort(l, new ExercisesComparator());
return l;
}
In terms of SQL you need an order by clause to guarantee the order of the returned rows. That is the behaviour you are seeing of '... displayed in the order that the exercises were created.' ... is actual specific to your DB's convention (and not portable across DB's).
If you want SQL result ordering by 'order that the relationship (between the workout and exercise) was created' ... then what that strictly means is that you need a db column to store that on the intersection table and have the SQL order by that column.
Now, with #ManyToMany by default the generated intersection table does not have a 'When Created' column. You can either model the intersection table explicitly (change from #ManyToMany to use 2 #OneToMany etc) or manually define your intersection table with a 'When Created' column and DB triggers to populate that ... and then reference that db column in an order by clause.

How to retrieve #Reference fields / which is best #Reference or #Embedded and SubQuery in MorphiaQuery

I have a mongodb with two model classes say User and UserInfo. The criteria is in User class I have to retrieve a multiple fields around 10 fields like "firstName","lastName", etc and in UserInfo Model class I like to retrieve only one field say "age".
At this moment I referenced the UserInfo class's object to the User class like stated below in the Structure and its stores in the DB as {"firstName","John"},{"lastName","Nash"},{userInfo: userInfoID} but if I make an Embedded Relation then it would store all the userInfo's fields and I think to retrieve one ("age") field it is Unwanted to Embed all the userInfo's fields which inturn will make the application slow I think.
Which scenario should I use whether #Reference or #Embedded, I think Embedded will slow down my response to DB but in the websites its given as reference annotation only slows down querying time and needs some sort of Lazy Loading an all, my structure is like below:
class User extends Model{
public String firstName;
public String lastName;
public String loginTime;
public String logoutTime;
public String emailId; etc,etc......
Some more 10 fields like this+userInfo reference object
#Reference
public UserInfo userInfo;
}
class UserInfo extends Model{
public String emailId;
public String age;
public String sex;
public String address;
public String bank; etc,etc......
Some more 10 fields like this
}
As I stated above I want only age field from UserInfo and all fields of User, so which Annotation is best and #Reference or #Embedded. It will be more helpful if I get a single query for User class in which I can retrieve all fields of User and only "age" field of UserInfo. In short I need a query like this when I go for #Reference relationship
field("userInfo.age") for userInfo.emailId = (MorphiaQuery q = User.createMorphiaQuery;
q.field("firstName").equal("John"); q.field("lastName").equal("Nash"); q.field("loginTime").greaterthan("sometime"))//the complex part where I need age of particular userInfo but I have only the ID of the userInfo since I am using Reference and that Id too got from a **subQuery**....
Please don't write two queries I need a single query or maybe a query with subquery. To be more clear I can tell in SQL language:
SELECT age FROM UserInfo where emailId = u.emailId
(SELECT * FROM User WHERE firstName='John' AND lastName='Nash' AND
logintime='someTime') AS u;
I need this exact same query without writing two morphia queries which consumes more time by referring two tables.
Mongo does not support query across tables / collections. And such page would satisfy you:
MongoDB and "joins"
As in sql, the join query is also build intermediate result set and make query again:
Understanding how JOIN works when 3 or more tables are involved. [SQL]
When you build your model, you should not consider a lot about what single query but structural modeling:
http://docs.mongodb.org/manual/core/data-modeling/
For your case, if you are using embeded, you can make this in one query and specify the fields you need by using queries like:
db.User.find({"some_field":"some_query"},{"firstName":1,....,"userInfo.age":1})
Check projections here:
http://docs.mongodb.org/manual/reference/method/db.collection.find/
If you are using reference or even soft link like using Morphia Key<> to lazy load the UserInfo, it requires two queries.
If it's not real-time application, you can also try mongo map-reduce to merge collection to handle big data, though the map-reduce is too bad for mongo though.
I'm reasonably sure you can't with just one query.

Hibernate mapping a specific field to be loaded by a table join

Is there a way I can map a field in an hibernate object to be loaded with a table query?
As an example lets say Table_Message has fields id(int),message_key(varchar),message_content(Clob),language(varchar). This table will hold messages in different languages(locale).
And another table thats mapped to an entity using hibernate. Comments with fields id(int),comment_message_id(varchar),created_date(datetime). comment_message_id refers to Table_Message's message_key column.
EDIT: Table_Message is NOT a mapped Entity in hibernate
Assuming my comment class is
public class Comment
{
int id;
String message;
Date createdDate;
}
Is there a way to tell hibernate to load message by joining Comment table and Table_Message table by message_key with a default locale (for example 'en').
Basically is there a way to tell hibernate to load a field by running a specific query? And if so what is that way?
I know about writing a Custom SQL query for loading the entity. But since I'm using XDoclet there doesn't seem to be a way to do that. Also it will be very convenient if there's a way to do that for a single field.
I guess ResultTransformer may help you in this. Please check
http://docs.jboss.org/hibernate/orm/3.3/api/org/hibernate/transform/ResultTransformer.html
http://stackoverflow.com/questions/6423948/resulttransformer-in-hibernate-return-null
You must join the tables by comment_message_id with message_key and further filter the result by language. I assume the message_key is unique.
As a side notice: you should use integer keys to have better performance.
You can try to write a database view in SQL and create an entity to opaque the view:
CREATE VIEW Comment_Table_Message AS
SELECT c.id, c.comment_message_id, c.created_date, m.id AS mid,
m.message_content, m.language
FROM Comment c, Table_Message m
WHERE c.comment_message_id = t.message_key;
Now you can create an entity CommentTableMessage and use JPQL to filter results by language:
SELECT x FROM CommentTableMessage x WHERE x.language=?1
If Table_Message was a Hibernate entity you would write (in JPA terms):
#Entity
public class Comment
{
int id;
#ManyToOne()
#JoinColumn(name="comment_message_id")
TableMessage tableMessage;
String message;
Date createdDate;
}
#Entity
public class TableMessage {
int id;
#Id
String messageKey;
bytes[] messageContent; //I don't know how you want to deal with Blobs?
String language;
}
Having that you can write a simple JPA Query: (Can you use JPA ? - next assumption)
SELECT c FROM Comment c WHERE c.tableMessage.language=?1

Mapping db-imported countries to address entity with JPA

I ran some DDL script to setup a complete country table in my database. The country table's primary key column contains the corresponding ISO code for every country.
In my JPA project I have a User entity having an embedded Address entity and this Address entity has a reference to a Country. The relationship between User and Address seems to be no problem to me, but the relationship between Address and Country. I tried to map it as a ManyToOne relationship, since many addresses can share a country.
Problem is: I annotated the iso member variable of the Country class with Id -> Now, JPA/Hibernate complains about not having set the id of the country manually. But in this case, the id is already given and set, since I imported the data once and the ISO code is unique and by db schema means declared as primary key. In this special case, there is no need for updates or inserts in the country table - the information should be read only!
Any idea what to do, so I can use my countries table without altering?
Your question is missing some details, so the following involves a lot of guessing :-)
Your Country class should look something like:
#Entity
#Immutable
#Table(name="countries")
public class Country {
#Id
private String isoCode;
// all other attributes, getters / setters, etc...
}
#Immutable is a Hibernate extension to JPA standard; you don't have to put it on entity but having it will result in slightly better performance. Keep in mind that it will really make the Country immutable - you won't be able to create / update / delete countries through your application. You may want to configure cache for your Country entity as well if it's used often enough.
Your Address would have the following mapping to country:
#ManyToOne
#JoinColumn(name="country_iso_code")
private Country country;
Note the absence of "cascade" attributes - you don't need any. The final important point here is you actually need to get or load the Country instance to set it on address:
Country country = (Country) session.load(Country.class, isoCode);
// OR
Country country = (Country) session.get(Country.class, isoCode);
address.setCountry(country);
...
session.saveOrUpdate(address);
The first line above will not hit the database; use it if you know that country with such an ISO code exists. Second form will hit the database (or cache, if configured) and return NULL if country with that code does not exist.

Categories