-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
allow fetching of lazy relations outside of its original transaction #38916
Comments
Hey @beikov , could you please share your opinion on the right solution for this using Hibernate ORM? I'd definitely be interested in hearing it. |
I think the proper solution is to always think about the what data you really need and model this with DTOs to avoid any sort of unpredictable lazy loading. with statements as (
select cast(:id) as id
union all
select s.previousStatement.id as id from Statement s join statements r on s.id = r.id
)
select s from Statement s where s.id in (select r.id from statements r) This way, you will select all statements that are reachable via Ideally, we'd offer a recursive fetch join to model this, but we don't have that yet. |
@beikov thanks for your feedback. In my use case:
As getting a statement is a O(1) operation, but fetching the whole chain is O(n), I do not want to fetch them eagerly or with any kind of projection or recursive fetch. Fetching lazily means that I have a best case of O(1) and a worst case of O(f) (f meaning a few here). The proposed alternative is O(n) best case, making it perform much worse (n can be big here). There are a lot of other valid use cases. Like the 'debate' about open-session-in-view for spring, there will always be PROs and CONs, and I suggest to not go too deep into this debate here but let users make conscious decisions about their use cases. Therefore, such a functionality is useful for some users. |
Then IMO you should model your different flows explicitly by creating separate data access methods that initializes exactly the state that the caller needs. Any sort of unpredictable lazy initialization will lead to pain, so it's best to avoid it in the first place.
Talking about
The only PRO I see is that you can turn off your brain and not think about the data access design, but that will bite you very fast when you run into performance issues, because changing the design later is painful. So I don't really view that as a PRO, but rather as a CON in disguise. Modelling your data fetching requirements is IMO not something that should be an after-thought just like performance testing shouldn't be, because at some point there will be that one user with hundreds of chained
If you really want this broken behavior, then let the transaction span across the whole request by annotating your HTTP endpoint method with |
This is quite opinionated, and I really have a different view and experience. Even if the recursive fetch is a single request, it is definitely a O(n) one that I want to avoid. As said, I most often do not need this data. Of course, we could replace the relation with an ad-hoc call. But that also means refactoring the database to replace the many-to-one by a relation table, which I don't consider better design for this use case. 'Turn off your brain' is rather though wording: I'd rather rephrase it as decoupling the service returning a Statement from whatever the next transaction wants to do with it. There is no use case where a user will bring down the whole application, for multiple reasons. This is also a very simple use case, but navigating complex object graphs not known in advance in a different transaction isn't bad design per se. Performance isn't an afterthought here, on the contrary. I'm new to this project, but having bigger transactions is one of the steps that we are taking further. Still, it doesn't cover all use cases and we sometimes do want a separate one. Of course, https://hibernate.atlassian.net/browse/HHH-17750 makes our problem worse: if fetching a lazy transient attribute of a detached entity would work in a new transaction, the need for this would be much lower (though still there). |
I was asked for an opinion and here you have it. I simply prefer designing the data access rather than burying requirements in e.g. the UI code through implicit lazy initialization. If you ask me, no entity should ever be serialized to JSON, but only DTOs. I understand that this might seem extreme to some folks, but designing the data access this way makes it way simpler to reason about performance and optimize, simply because there are no unknowns.
That's totally understandable, hence my suggestion.
I didn't suggest that and I agree that this would not improve the design.
I don't know your application design, but it seems to me that your app could know beforehand what the next transaction requires and hence load the appropriate data without the need for lazy loading. It's a matter of creating separate data access methods for the respective flows.
No idea how you are "navigating" in "a different transaction", but maybe you you can solve your issues by simply loading the subsequent object graphs explicitly instead of relying on lazy loading?
It seems @mbladel is already looking into this issue, so let's maybe keep the discussion about the Jira issue in the Jira comments. |
This isn't UI code. The first transaction is simply committed and we end up with an object with lazy relations.
I do strictly agree with that. Not what is happening in my example.
There are unknowns, the first service doesn't know how much data it has to send back. Even the caller doesn't know at call time, it might need to fetch relations. It's the essence of lazy relationships, the fact they are in a different transaction doesn't make this different.
No, at the time of the first call, I can't decide how many previous statements I need. It also depends on expensive calculations on the statement itself (calling third party services, ...). I really can't know beforehand.
So, basically, this is not the case. I also don't see different kind of structures for slightly different use cases as a better design. Your proposal does have its merits to optimize some use cases, but isn't the holy grail either. JPA is a leaky abstraction, and that's both a problem and some of its strengths. Lazy loading, even in a different transaction, is quite useful in some scenarios. I do agree it can be misused.
That means you need to know all the paths that you'll have to follow beforehand. If there is any complex logic or dependencies, this isn't necessarily the case.
Yes. I just meant this is the first reason why we wanted this feature, even it were only for migration. |
Maybe I'm not totally understanding, but in your second transaction, you can execute your logic to determine what data needs to be fetched and then load the |
In the second transaction, I can't know if I need a 3rd or 4th object before checking the second one. How would an ad-hoc call to fetch a new statement be different from a lazy fetch regarding performance? I'm still in the service layer btw. |
maybe another perspective: we are blocked right now in migrating to quarkus since the existing codebase was using eclipselink and making excessive use of lazy-loading data outside of an active session. While this is something we would like to change, the effort required to refactor the persistence layers in all packages is too much effort, and the service constraints (how it's used, what the code does...) justifies living with it. We tried configuring the |
@lazyBisa I do agree for sure, and migration is part of my issue too. To be honest: though different point of views on detached entities are somewhat interesting, this is irrelevant here as this is what we choose to use in our own projects. Being able to merge detached entities with lazy fields sounds like a basic use case, regardless of how other people like the pattern or not. JPA is a leaky abstraction, and the arguments I heard here seem to be against some core functionalities of JPA. This doesn't seem like the best place for this discussion IMHO. Anyway, it seems this was considered as a Hibernate bug and fixed in the upcoming 6.4.5 release, which looks almost ready in Jira: see https://hibernate.atlassian.net/browse/HHH-17750 & https://hibernate.atlassian.net/projects/HHH/versions/32262/tab/release-report-all-issues So, I guess the next step is now to simply wait on that release + integration of Hibernate core 6.4.5 in Quarkus? |
Correct. That will lead to closing #38807, while this specific issue (open session in view for Quarkus, essentially) will still be open for discussion / up for grab. |
Description
As a follow-up to #38807, I think it would be very useful to be able to access lazy relations of detached entities:
Example use case for which it would be practically needed: chained relations, e.g. a
Statement
has apreviousStatement
; you want to be able to fetch the previous statement, you'll maybe need the next one afterwards etc. Performance is often mentioned in the CONs, but you would end up fetching the objects one by one when needed anyway, detached entity or not, and an open-in-view mechanism wouldn't make any difference there. On the contrary, it will in some situations be more performant: the relation will only be queried from the database if/when needed instead of being fetched in all cases, eagerly or from a projection.I suggest implementing a similar feature in Quarkus.
Implementation ideas
No response
The text was updated successfully, but these errors were encountered: