One to one relationship in hibernate - java

I am implementing one to one (one Employee<-> one Mobile) relationship in hibernate as follows. This code works fine, but as this is one to one relationship, assigning same mobile number to emp1 and emp2 should have created problem (it violates relationship) but code is accepting and adding 2 emps with same mobile (Confirmed from Database tables). Why is hibernates one to one relationship like one mobile<->many employees?
My Code:
#Entity
public class Employee {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String name;
#OneToOne
private Mobile mobile;
//...rest of the code
}
#Entity
public class Mobile {
#Id #GeneratedValue
private int id;
private long number;
//...rest of the code
}
Test Client main(...){
Mobile mobile = new Mobile(9999999999L);
Employee emp1 = new Employee("Raja");
Employee emp2 = new Employee("Raja");
emp1.setMobile(mobile);
emp2.setMobile(mobile);// VIOLATING 1-1 RELATIONSHIP
//...REST OF THE COMMON CODE
session.save(mobile);
session.save(emp1);
session.save(emp2);
session.getTransaction().commit();
}
DATABASE SHOWS BOTH EMP RECORDS WITH SAME MOBILE NUMBER (VIOLATION OF 1-1)

For one to one relations, you should always make sure that you have a unique constraint on your database (either generated by hibernate or manually created).
Hibernate won't check it because it would require to collect extra data every time to do the check. The database can do it more efficiently.
To do the check, hibernate would have to do an extra query. And if the database is configured correctly that extra query would cost time and resources without any gain.
If you don't have unique constraints and you define the relation bidirectional, you can get even more trouble.
Hibernate will save the conflicting records without complaining as you already discovered. And it would become impossible for hibernate to use the relation starting from the object on the other side (getting the Employee via the Mobile in your case). If mobile would be configured to get it's related employee eagerly, it would become impossible to get the mobile in memory after both employee's where saved.

Related

Hibernate good practice, lazy/eager loading and saving/deleting children (help me Hibernate sensei)

So, I have found myself in quite a pickle regarding Hibernate. When I started developing my web application, I used "eager" loading everywhere so I could easily access children, parents etc.
After a while, I ran into my first problem - re-saving of deleted objects. Multiple stackoverflow threads suggested that I should remove the object from all the collections that it's in. Reading those suggestions made my "spidey sense" tickle as my relations weren't really simple and I had to iterate multiple objects which made my code look kind of ugly and made me wonder if this was the best approach.
For example, when deleting Employee (that belongs to User in a sense that User can act as multiple different Employees). Let's say Employee can leave Feedback to Party, so Employee can have multiple Feedback and Party can have multiple Feedback. Additionally, both Employee and Party belong to some kind of a parent object, let's say an Organization. Basically, we have:
class User {
// Has many
Set<Employee> employees;
// Has many
Set<Organization> organizations;
// Has many through employees
Set<Organization> associatedOrganizations;
}
class Employee {
// Belongs to
User user;
// Belongs to
Organization organization;
// Has many
Set<Feedback> feedbacks;
}
class Organization {
// Belongs to
User user;
// Has many
Set<Employee> employees;
// Has many
Set<Party> parties;
}
class Party {
// Belongs to
Organization organization;
// Has many
Set<Feedback> feedbacks;
}
class Feedback {
// Belongs to
Party party;
// Belongs to
Employee employee;
}
Here's what I ended up with when deleting an employee:
// First remove feedbacks related to employee
Iterator<Feedback> iter = employee.getFeedbacks().iterator();
while (iter.hasNext()) {
Feedback feedback = iter.next();
iter.remove();
feedback.getParty().getFeedbacks().remove(feedback);
session.delete(feedback);
}
session.update(employee);
// Now remove employee from organization
Organization organization = employee.getOrganization();
organization.getEmployees().remove(employee);
session.update(organization);
This is, by my definition, ugly. I would've assumed that by using
#Cascade({CascadeType.ALL})
then Hibernate would magically remove Employee from all associations by simply doing:
session.delete(employee);
instead I get:
Error during managed flush [deleted object would be re-saved by cascade (remove deleted object from associations)
So, in order to try to get my code a bit cleaner and maybe even optimized (sometimes lazy fetch is enough, sometimes I need eager), I tried lazy fetching almost everything and hoping that if I do, for example:
employee.getFeedbacks()
then the feedbacks are nicely fetched without any problem but nope, everything breaks:
failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session
The next thing I thought about was removing the possibility for objects to insert/delete their related children objects but that would probably be a bad idea performance-wise - inserting every object separately with
child.parent=parent
instead of in a bulk with
parent.children().add(children).
Finally, I saw that multiple people recommended creating my own custom queries and stuff but at that point, why should I even bother with Hibernate? Is there really no good way to handle my problem relatively clean or am I missing something or am I an idiot?
If I understood the question correctly it's all about cascading through simple 1:N relations. In that case Hibernate can do the job rather well:
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "post", orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
}
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private Post post;
}
Code:
Post post = newPost();
doInTransaction(session -> {
session.delete(post);
});
Generates:
delete from Comment where id = 1
delete from Comment where id = 2
delete from Post where id = 1
But if you have some other (synthetic) collections, Hibernate has no chance to know which ones, so you have to handle them yourself.
As for Hibernate and custom queries, Hibernate provides HQL which is more compact then traditional SQL, but still is less transparent then annotations.

Schema migration of ids and n to m relationships in objectify

If have a User entity which previously had a String id. I'd like to migrate to a Long id which seems simple:
public class UserEntity {
/*#Id*/
#Index String oldId;
#Id Long newId;
/* other indexed fields I use for loading the entity */
private List<Ref<ReferencedEntity>> collections;
}
public class ReferencedEntity {
private List<Ref<User>> owners;
}
Since I load the user via different fields I can check if the user has a null newId and if so just null the old one save it back so the auto generator will set a new Id in the newId field.
The problem is now my n to m relationship to other entities. How should I migrate those? I have a Ref on both sides so I guess I just can load the refs on the user entity side and replace the other side with the new Id.
The general question is how to migrate n to m relationship if one side needs a new id?
You are probably thinking with a relational database strategy. When you update a value on one side, the other side will not be updated, therefore you have to update both entities. Since this is NoSql you have to think differently.
I would take this strategy.
First load the "old" user entity and save it again in the new structure. Once you have confirmed that all the data you loaded has been converted to the new object (I suggest using BigQuery), then you should spawn a task for each referenced entity using the indexed oldId and update the reference of the owners in the ReferencedEntity.
It will take a while to load but it is probably a safe way to do it.

Mapping one DB column to two seperate fields using JPA

I'm developing a code generator that have to generate JPA entities from database meta-model files. These model are from home-brewed modeling system which are being used to generate models other than JPA entities.
In these models some fields are mapping back to same database column. But it seems like JPA does not like that very much. When I try to run generated code I get
Exception [EclipseLink-48] (Eclipse Persistence Services - 2.6.0.v20140809-296a69f): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Multiple writable mappings exist for the field [FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]. Only one may be defined as writable, all others must be specified read-only.
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[TransactionIdKey-->FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]
Descriptor: RelationalDescriptor(InventTransHistFactDM --> [DatabaseTable(FACT_INVENT_TRANS_HIST_DM)])
As I can't change the models only option left is to make one of those fields read-only. And the JPA entities being generated are only used to read data from database it will not used for writing data. Is there a way to mark some fields as read only or tell EclipseLink that these entities are read only so it does not have to worry about the multiple writable mapping.
I tried using EclipseLink's #ReadOnly annotation in all entities but it did not help this issue.
There is no #ReadOnly in JPA.
There are however attributes "insertable"/"updatable" that you can set against a field via #Column to effectively do the same.
The question may be almost 6 years old, but it's still being found today, so I'd like to address another option:
public class Foobar {
#OneToOne
#JoinColumn(name="SELF_COLUMN_FOO", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Foo foo;
#OneToOne
#JoinColumn(name="SELF_COLUMN_BAR", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Bar bar;
}
This can be used where SELF_COLUMN is obviously the relevant column in the Foobar table, and FOREIGN_COLUMN_TO_JOIN would be single key in the other table you wish to join.
This will be useful where you want to have two (or more) attributes in a single class, but only one column to join on the foreign DB table. For example: An Employee may have a home phone number, cell number, and a work phone number. All are mapped to different attributes in the class, but on the database there's a single table of phone numbers and id's, and an identifier column, say VARCHAR(1) with 'H' or 'W' or 'C'. The real example would then be...
Tables:
PHONENUMBERS
PHONENUMBER_ID,
ACTUAL_NUMBER
EMPLOYEE
ID
HOMENUMBER VARCHAR(12),
CELLNUMBER VARCHAR(12),
WORKNUMBER VARCHAR(12)
public class Employee {
#OneToOne
#JoinColumn(name="HOMENUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone homeNum;
#OneToOne
#JoinColumn(name="CELLNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone cellNum;
#OneToOne
#JoinColumn(name="WORKNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone workNum;
}
As you can see, this would require multiple columns on the Entity's table, but allows you to reference a foreign key multiple times without throwing the 'Multiple writable mappings exist...' that you showed above. Not a perfect solve, but helpful for those encountering the same problem.

My code throws org.hibernate.ObjectDeletedException exception

I have a class of users that includes a list of books objects. Once user's record is created all the books that he borrows will added to the list of books in his object. Once user removes his profile the record and history of the borrowed books need to be kept.
Many registered users may frequently borrow books and delete their profiles. To make the information retrieval faster (for example, retrieve a list of active users) I do not want to keep the record of deleted profiles in the same table that I keep records of active profiles.
Currently once user wants to delete his profile I put that object (userObj) in deactiveUserObj and remove the user's record from User table and try to save the deactiveUserObj to keep the record of deactivated record in that table but it throws the following exception.
My options: I know that I can create another table and keep the id of active users there or have an extra column of type boolean for user class and make it false to indicate the record is deleted. However I am not sure which of these three approaches is better or if there is another approach that I have not considered.
User class
#Entity
public class User{
private long id;
private List<Book> books;
...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<Book> getBooks() {
return books;
}
....
Book class
#Entity
public class Book{
private long id;
private string name;
...
}
Deleted Object class
#Entity
public class DeactiveUsers{
private long id;
private date deactiveSince;
private List<Book> books;
...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public List<Book> getBooks() {
return books;
}
....
}
MyCode
1) code to retrieve an object of user class goes here
2) code to copy object of user class into deletedObject goes here
3) 3.1 - session.delete(userObj);
3.2 - session.saveOrUpdate(deactiveUsersObj);
The code runs into following error
SEVERE: org.hibernate.ObjectDeletedException: deleted object would be re-saved by
cascade (remove deleted object from associations): [com.project.Book#1]
First: The reason for your error is you have cascade set to ALL on User.getBooks(), which will delete its books when you delete the User. Then, in the same session, you attempt to re-add the deleted objects when you add the DeactivatedUser back (which contains the same Books -- while you don't show your code for constructing a DeactivatedUser from a User, I presume you are making a shallow copy of Books, as you should). Change cascade to SAVE_UPDATE.
Second: I highly recommend keeping them all in the same table with a flag field that indicates if they are deleted or not.
Do not optimize prematurely. Unless you've actually benchmarked your code and found a bottleneck in database queries related to active vs. inactive users, there's no benefit to over-complicating your schema and business logic. Most SQL servers are very efficient, it is unlikely that you will find a bottleneck there for your application.
Doing it this way, an easy way to improve performance is to simply create an index on the deleted flag field.
With Hibernate you will be able to easily select active vs. deleted users via a Criteria.
Additionally, and most importantly, by not having a separate User and DeactiveUser object, you won't have to maintain the two objects in parallel, and you won't have to duplicate code that can operate on both types.

How to load the id, but not the object if it doesn't exist in a ManyToOne mapping

I have two objects Customer and Transaction. They look something like this:
#Entity
class Transaction {
#Id
long id;
#Column("CUSTOMER_ID")
long customerId;
#ManyToOne
#JoinColumn(name="CUSTOMER_ID", insertable=false, updatable=false, nullable=false)
Customer customer
...
}
#Entity
class Customer {
#ID
long id;
...
}
The problem is we use fake customer numbers to identify some types of transactions. It's a legacy practice that I can do nothing to change and various other processes outside my control rely on it.
What I'd like to be able to do is get the customer number back for the dummy customer transactions and just have a null customer on my transaction object. For transactions that aren't to dummy customers I'd like to have the customer object set. Ideally I'd rather not have to test for the dummy customer numbers and issue separate queries.
How do I write a query and map my object to do that? Right now everything I try, I end up with an EntityNotFound exception for the dummy policy IDs.
using UNION you could select the proper customer record, unioned with a default 'NO SUCH CUSTOMER' record, make sure the default value comes second by ordering apropriatly and fetching only the first result.
In JPA if the ManyToOne does not reference anything you should just get null. Not sure how you are getting EntityNotFound. It may be a specific issue to your JPA provider.

Categories

Resources