Sample code to represent my problem.
Let's say there are entity classes like that
public class User {
...
#ManyToOne
#JoinColumn(name = "users_statuts_id", referencedColumnName = "id", nullable = false)
private UserStatus status;
}
public class UserStatus {
...
#OneToMany(mappedBy = "status")
private List<User> users = new LinkedList<>();
}
This works as expected but, problem starts when I want to use MapStruct generated mapper and DTO object. I will get StackOverflowError becouse of Cyclic references User->UserStatus->List< User>-> .....
And take into account that not necessary it will be in pattern A->B->A (User->UserStatus->User)
sometimes it will be User->ClassA->ClassB->...->User
I tired adding Context (CycleAvoidingMappingContext) to mapper as stated in other threads to break cycle but I failed and still get StackOverflowError. Tried too with AfterMappings and Mapping(ignore) but still not worked and I would like to avoid settings manually nulls to break the cycle.
I wonder if is possible to break cycle similar to #JsonIdentityInfo but on Hibernate level ?
My classes are a bit more complicated and bidirectional relation would help a lot later. Someone have some tips how should I make it done to work properly ?
Related
I have a bit of a catch-22 that I need some help wrapping my head around. I have two EAR applications, 'Product' and 'Manufacturer'. I also have a 'Common' library jar with a bunch of shared code.To avoid circular dependencies I don't want either application to directly depend on the other. To do this, I have abstract #MappedSuperclass versions of my domain classes (also called Product and Manufacturer) in the common library. So far so good.
The problem comes when I need to define some relationships between the classes. The child version of Product has a 'primary manufacturer' property defined like this:
#ManyToOne
#JoinColumn(name = "primary_mfg_id")
protected common.domain.Manufacturer primaryMfg;
and a collection of manufacturers:
#ContainedIn
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "products_to_manufacturers", inverseJoinColumns = {
#JoinColumn(name = "manufacturer_id", referencedColumnName = "id") }, joinColumns = {
#JoinColumn(name = "product_id", referencedColumnName = "id") })
#OrderBy("name")
#AuditJoinTable(inverseJoinColumns = { #JoinColumn(name = "manufacturer_id", referencedColumnName = "id") })
protected Set<common.domain.Manufacturer> manufacturers;
If I try to deploy that I will get an error saying that #ManyToOne references an unknown entity type. That makes sense, Manufacturer is a MappedSuperClass not an entity. Unfortunately, I can't think of a way to solve this without major headaches. I could combine the parent and child Manufacturer classes, but that would require me to also move a good portion of the domain model for the Manufacturer application into the common jar. I could have a Product specific version of the Manufacturer class but that would require adding a DTYPE to the table that is totally meaningless outside of hibernate.
I feel like there must be a cleaner way to structure this code, but I'm all out of ideas. Any help would be appreciated.
Update:
I was able to work around the 'primaryMfg' property by changing it to an id and looking up the manufacturer via the entity manager when I need the full object. Unfortunately, I can't figure out a way to do that for the 'manufacturers' property so I am still stuck.
Update 2:
Using the ORM mapping solved the compile issues, but there are still runtime problems. Those issues go beyond the scope of the original question so I have posted another question here: Getting An Instance Of A Mapped Superclass With Hibernate EntityManager
Turns out the solution was staring me in the face the whole time. While I was looking at my orm.xml file I happened to notice some commented out code that I must have disabled during migration. I uncommented it and the problem went away. For anyone else who happens to run into this, the snippet was:
<entity class="com.tura.common.domain.Manufacturer" name="Manufacturer">
<table name="manufacturers" />
</entity>
By adding that in the orm.xml of Product, it is telling hibernate to treat the parent class as an entity.
I have an #ElementCollection Map<User, Long> permissions in a class.
#ElementCollection
#CollectionTable(name = "als_permission", joinColumns = #JoinColumn(name = "File"))
#MapKeyJoinColumn(name = "User")
#Column(name = "Permission")
#JsonIgnore
private Map<User, Integer> permissions = new HashMap<>();
I made some changes on that collection only, and invoke repo.save(entity). I see the record does get updated, but my #PostUpdate handler which is defined in #EntityListeners does not appear to be called.
Is there something I have to put to indicate it? I am thinking I may need to have some sort of cascade somewhere.
Short Answer: No.
I had the same issue came to the conclusion that this is not possible at the moment. Mainly because intended or not implemented yet, see: https://github.com/eclipse-ee4j/jpa-api/issues/167
For the testing I created a repository to find any event I could consumer for the given purpose: https://github.com/HannesRakete/so-jpa-element-collection-events
Workaround 1
Add a #Version for optimistic locking on the parent entity.
Workaround 2
Migrate to another association-type, see https://thorben-janssen.com/hibernate-tips-query-elementcollection/
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.
So here's my situation: I want to build a simple CRUD webservice using Jackson and Hibernate. Seems like a perfect job for Spring Boot. So we have the following:
(Please note that I am condensing the code so its not compile-able)
class Doctor {
#Id
long id;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "doctor_service", joinColumns = { #JoinColumn(name = "doctor_id", nullable = false) }, inverseJoinColumns = { #JoinColumn(name = "service_id", nullable = false) })
Set<Service> services;
}
class Service {
#Id
long id;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "services")
Set<Doctor> doctors;
}
A simple data model. And we have a simple requirement: on the webservice, when we get Service objects we should get the associated Doctors. And when we get the Doctors, we should get the associated Services. We are using lazy because [insert justification here].
So now lets serve it:
#Path("/list")
#POST
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public JsonResponse<List<Doctor>> list() {
return JsonResponse.success(doctorCrudRepo.findAll());
}
Gloss over the JsonResponse object (just a convenience blackbox for now) and lets assume the doctorCrudRepo is a valid instance of CrudRepository.
And the firestorm begins:
failed to lazily initialize a collection of role: Doctor.services, could not initialize proxy - no Session (through reference chain: ...)
Ok so Lazy doesnt work then. Simple enough. Just make it eager.
Caused by: java.lang.StackOverflowError: null
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:455)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:367)
at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:655)
... 1011 common frames omitted
So let's see what other people have said:
Contestant #1: Solutions are not relevant because they apply to one-to-many, not many-to-many, so I still get the StackOverflowError.
Contestant #2: Same as before, still a one-to-many, still StackOverflow.
Contestant #3: Same (has nobody ever used many-to-many???)
Contestant #4: I can't use #JsonIgnore because that means it will never be serialized. So it doesn't fit the requirements.
Contestant #5: At first glance, it appears to work fine! However, only the Doctor endpoint works - it is getting the services. The Services endpoint doesn't work - its not getting the Doctors (empty set). It's probably based on which reference defines the join table. This again doesn't fit the bill.
Contestant #6: Nope.
Some other solutions which are wrong but worth mentioning:
Create a new set of objects for json serialization that are not wrapped by hibernate, and then copy the properties over in the controller. This is a lot of extra work. Forcing this pattern everywhere defeats the purpose of using Hibernate.
After loading the Doctor, loop over each Service and set the service.doctors to null, to prevent further lazy loading. I'm trying to establish a set of best practices, not to come up with hackish workarounds.
So... what's the RIGHT solution? What pattern can I follow that looks clean and makes me proud to use Hibernate and Jackson? Or is this combination of technology so incompatible to suggest a new paradigm?
Firstly, regarding your statement "...copy the properties over in the controller. This is a lot of extra work. Forcing this pattern everywhere defeats the purpose of using Hibernate.":
It doesn't defeat the purpose of using Hibernate. ORMs were created in order to eliminate necessity of converting database rows received from JDBC to POJOs. Hibernate's lazy-loading purpose is to eliminate redundant work on writing custom queries to RDBMS when you don't need great performance, or you are able to cache the entities.
The issue is not with Hibernate&Jackson, but with the fact that you are trying to use the instrument for a purpose, it was never designed for.
I guess that your project tends to grow (usually they all do). If that's true then you will have to separate layers someday, and better sooner than later. So I would suggest you to stick to the "wrong solution #1" (create a DTO). You can use something like ModelMapper to prevent hand-writing of Entity to DTO conversion logic.
Also consider that without DTOs your project may become hard to maintain:
Data model will evolve, and you will always have to update your front-end with changes.
Data model may contain some fields, that you may want to omit from sending to your users (like user's password field). You can always create additional entities, but they will require additional DAOs, etc.
Someday you may need to return user a data that is a composition of some entities. You can write a new JPQL, like SELECT new ComplexObject(entity1, entity2, entity3) ..., but that would be much harder than to call few service's methods and compose the result into DTO.
I found a solution that seems to be elegant.
Use OpenEntityManagerInViewFilter. Seems to be frowned upon (probably for security reasons, but I haven't seen any convincing reason to not use it). It's simple to use, just define a bean:
#Component
public class ViewSessionFilter extends OpenEntityManagerInViewFilter {
}
Use LAZY on all references. This is what I wanted to begin with, and it's especially important since my data has many references and my services are small.
Use #JsonView. See this helpful article.
First, figure out what the views will be (one for doctors, one for patients)
public interface Views {
public static interface Public {}
public static interface Doctors extends Public {}
public static interface Services extends Public {}
}
Looking from the Doctors view, you will see the services.
#Entity
#Table(name = "doctor")
public class Doctor {
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "doctor_service", joinColumns = { #JoinColumn(name = "doctor_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "service_id", nullable = false) })
#JsonView(Views.Doctors.class)
private Set<Service> services;
}
And looking from the Services view, you will see the doctors.
#Entity
#Table(name = "service")
public class Service {
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "services")
#JsonView(Views.Services.class)
private Set<Doctor> doctors;
}
Then assign the views to the service endpoints.
#Component
#Path("/doctor")
public class DoctorController {
#Autowired
DoctorCrudRepo doctorCrudRepo;
#Path("/list")
#POST
#Produces(MediaType.APPLICATION_JSON)
#JsonView(Views.Doctors.class)
public JsonResponse<List<Doctor>> list() {
return JsonResponse.success(OpsidUtils.iterableToList(doctorCrudRepo.findAll()));
}
}
Works perfectly for a simple CRUD app. I even think it will scale well to bigger, more complex apps. But it would need to be maintained carefully.
I have stumbled upon a really annoying situation: I am using Hibernate & Spring as backend for my app and it seems that in some cases, the entities that are in a relationship with a particular entity are not fetched as normal entity objects from the DB, but as Javassist types. E.g.:
I have the Campaign entity with the following relationships:
#Entity
#Table(name = "campaign")
public class Campaign implements Serializable {
[..]
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(uniqueConstraints = #UniqueConstraint(columnNames = {
"campaign_id", "dealer_id" }), name = "campaign_has_dealer", joinColumns = { #JoinColumn(name = "campaign_id", nullable = false) }, inverseJoinColumns = { #JoinColumn(name = "dealer_id", nullable = false) })
private List<Dealer> dealers = new ArrayList<Dealer>();
#ManyToMany
// (fetch = FetchType.LAZY)
#JoinTable(uniqueConstraints = #UniqueConstraint(columnNames = {
"campaign_id", "sales_area_id" }), name = "campaign_has_sales_area", joinColumns = { #JoinColumn(name = "campaign_id", nullable = false) }, inverseJoinColumns = { #JoinColumn(name = "sales_area_id", nullable = false) })
private List<SalesArea> salesAreas = new ArrayList<SalesArea>();
}
Upon retrieving the salesAreas connected to this Campaign, I get a list of SalesArea_$$_javassist_56, while for the dealers, I get normal Hibernate entities. Since the client part is based on GWT, we use RequestFactory for retrieving stuff. I initially thought it was a problem with the proxies, locators and so on but I have set a breakpoint in the service where these are retrieved and they are Javassist objects directly after selecting them. It seems that even removing the FetchType.LAZY annotation (although definitely not a desirable solution), the same thing happens. This happened also with other types of relationships, not only #ManyToMany.
We are using GWT 2.3, Spring 3, Hibernate 3.6.3 and JPA 2.0 for annotations.
Any suggestions would be appreciated.
Thanks in advance
As far as I can see the big problem that you're having is not so much the fetch type of your association, but rather that the proxied types don't work well with RequestFactory.
Yes, it could be solved by changing the fetch strategy but that sounds rather like a weak workaround that may break upon weird circumstances.
I don't remember exactly how to solve it, but I did, and as far as I remember there was an extension point in the ServiceLayerDecorator class. Basically there you check if the object you're returning is a Hibernate proxy (check Hibernate and HibernateProxy classes) and then return the non-proxy type instead in ServiceLayerDecorator. (http://code.google.com/p/google-web-toolkit/issues/detail?id=6767)
As for your fetch strategy, I'd largely recommend #BatchSize(N) where N is big (maybe 1000), but this is an independent subject.
Good luck!
If you call to the static method:
HibernateProxyHelper.getClassWithoutInitializingProxy(entity);
you get the class of the proxied entity and the class itself if it wasn't proxied.
With Hibernate's proxy model and now with it's use of Javassist to help avoid the slower traditional Hibernate run time reflection operations things will never quite be as elegant as the clean, intuitive experience people who use full bytecode enhancement solutions like JDO implementations (eg DataNucleus) enjoy.
Personally I can never see the sense in persisting (pardon the pun) with solutions that cause so many problems and fill the web with questions about broken code that requires strange, unintuitive workarounds but still people do...
However, back to the question: one solution to your problem, if you're using JPA, is to use DataNucleus/JPA which brings many of the benefits of DataNucleus/JDO (clean underlying implementation - no proxies, no Javassist classes etc.,) in a JPA compliant implementation - i.e. you don't need to change your existing source code to start using it.