So I am working now on a RESTful API that acts as a layer between the client and our databases, I'm currently using Hibernate for ORM and I've been trying for days now to find a solution to my problem, searching everywhere to no avail.
I can't post the actual code I'm using because its too big, but picture this:
I have a class Employee
mapped as an Hibernate entity that looks like this
@Entity
public class Employee {
@Id
Integer id;
String name;
Double salary;
String department;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "company_id")
Company company;
// Getters/Setters ommitted
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", salary=" + salary +
", department='" + department + '\'' +
", company=" + company +
'}';
}
}
And another entity class Company
that looks like this:
@Entity
public class Company {
@Id
Integer id;
String name;
String address;
@OneToMany(mappedBy = "company", fetch = FetchType.LAZY)
List<Employee> employees;
// Getters and setters ommited
@Override
public String toString() {
return "Company{" +
"id=" + id +
", name='" + name + '\'' +
", address='" + address + '\'' +
", employees=" + employees +
'}';
}
}
The database looks something like this:
Employee:
| id | name | salary | department | company_id |
|----|----------|--------|------------|------------|
| 1 | john doe | 3000 | Finance | 1 |
| 2 | mary sue | 3300 | HR | 1 |
Company:
| id | name | address |
|----|------|------------|
| 1 | ACME | St. Sesame |
Now lets say I want to get data from a certain employee, I want only the employee data + the ID of the company (and only the ID) as a reference if I need the company data later. It would look something like this:
Session session = SessionManager.openSession();
Employee emp = session.get(Employee.class, 1);
session.close();
Now I want to show the employee data
System.out.println(emp);
The output that is expected (or at least, that I expected) here is something like this:
Employee{id=1,name='john doe',salary=3000.0,department='Finance',company=Company{id=1,name=null,address=null,employees=null}}
That's simple enough, the problem is that it will throw an LazyInitializationException, because Hibernate tried to use a proxy object to fetch de Company data when it was required on the toString()
call of Employee, but the session is already closed.
The thing is if I closed the session without fetching any more data, then THATS IT, no more data needed, HIBERNATE! If I open a session, I will fetch all the data that I need, and ONLY the data that I need, and after I close it, it means that I dont need any more data, thank you hibernate, but Hibernate's proxy objects keep active even after the session is closed.
I've searched all around the net, even here on StackOverflow, and there are some so called "solutions" or "workarounds", like:
- Why don't you use HQL with constructors?
Well I actually use those, on many use cases, when the query is simple enough to use HQL. The problem is many use cases require complex querys, with lots of joins and calculations that even use tables that are not mapped to Hibernate Entities, so I need an Native Query to do so.
- You could use HQL with constructors, and create entities for every database table you need to access on the query
That would mean mapping dozens of entities that I would use on one, maybe two use cases, and never again, just so I could use HQL with Constructors. Besides, some of those queries use native functions and other complex calculations HQL simply does not support.
- Then use an Native Query, and you can put
NULL as columnName
when you dont want that column data
I do that too on some use cases, the problem is when that column is a join column on an entity relationship, Hibernate sets a proxy object, and I don't want an proxy object, just the data of the join column if, AND ONLY IF, that data is needed on another part of the code, where I can open a new session and retrieve the required data.
- Why don't you just keep the session open using that open session on view pattern?
No can't do, thats a RESTful API, what if the client asks for the employee data on a first request, and then later makes another request for the company data of that previous employee, passing the company ID on the body of the request? Thats a common use case.
- session.detach()?
Doesn't work.
- Stateless sessions?
Still creates proxy objects, it only disables first and second level caches for the current session.
- Why don't you use DTOs?
And create a new class for each use-case of partial data request of my API? HAHAHA!
You see, the only reason I use an ORM solution like Hibernate is because I dread manual handling of ResultSets do DEATH. Forget about HQL, Criteria API, etc. I don't mind creating native queries from Strings if I can only have Hibernate to map the ResultSet to an Object (Or list of objects) for me. And I want Hibernate to know that if there is an entity relation that I did not fetch before the session is closed, that means that I do NOT want to fetch it, so no proxy objects.
I know that there probably is another ORM solution out there that does not have this issue, but I can't give the time to learn it because DEADLINES, so someone help me please.
EDIT
So some of you marked my question as a duplicate of Converting Hibernate proxy to a real entity object, but there are differences:
That question revolves around how to "Unproxy" an object, as in how to trigger the initialization of an Hibernate proxy.
My question is about how to make Hibernate NOT use a proxy for an unfetched relation object, and instead just use an instance of the relation's class with only the ID of it.
In my previous example, instead of using a proxy to lazy fetch the Company
object related to the Employee
later when needed, I would like for hibernate to just create a new Company(company_id)
and leave it like that, NO PROXY.
EDIT 2
I just found out that the Jackson project (which I use for reading/writing data as JSON in my API) has a module that ignores Hibernate proxies when serializing objects.
Maven dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>${jackson-version}</version>
</dependency>
Then just register the module on you Object Mapper
instance:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate5Module());