-
Notifications
You must be signed in to change notification settings - Fork 59
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
stateless entity manager #374
Comments
As I see it, all the JPA implementations are capable (note 4) of providing what I used to call back in the day a "Sessionless API" (or "Sessionless ORM (3)", there are a few ORMs in Java and other languages that work this way). The "Sessionless API" has transactions (begin, commit, rollback etc) + insert, update, delete, flush, refresh, query. Relative to JPA, users of this API only need to manage the scope of transactions, that is they generally do not need to manage the scope of EntityManager and Transactions. In JPA terms, if we say that the dominant use of JPA is "transaction scoped persistence context" where both EntityManager and Transaction pretty much have the same scope then you might get a feel for how these "Sessionless ORMs" generally work which is that the Transaction internally has a "persistence context" (2). Lets say we have: And we use it like: try (Transaction txn = sessionLessJPA.beginTransaction()) {
sessionLessJPA.insert(<some new entity bean>);
List<CustomerEntity> customers = sessionLessJPA.query(CustomerEntity.class)
.where() ...
.findList();
sessionLessJPA.update(<some fetched entity bean>);
txn.commit();
} So the API is arguably simpler because we only need to manage the scope of transactions [somewhat because the scope of the L1 cache/persistence context matches the transaction (1)]. To me, it looks like the JPA API was somewhat designed around EntityManager being managed by a "container" (using The other thing that "Sessionless ORMs" do is remove the need for managing the entity bean lifecycle. Effectively they do this by (A) having the old values/dirty values stored on the entity beans themselves (B) having the JDBC batch mechanism also scoped to the transaction. This means that persisting methods like The result is that the persist methods insert, update, delete don't actually need to interact with the "persistence context" because they have their own dirty state with the caveat that delete()s need to remove entries from the "persistence context". Disclaimer:I'm totally biased to "Sessionless ORM" because that is the type of ORM I maintain and use. I think a few of you probably know that. Notes:(1) Note that technically we could detach the "persistence context" from the transaction and similar attach a "persistence context" to a transaction along the lines of extended persistence context - but in practice that will be very rarely used. |
@rbygrave So that sounds quite similar to what I described, except for one thing. In what I'm proposing, there's never a need to call In your proposal it seems that there's still transactional state held in the session since operations are queued until Now, given that my "stateless sessions" are truly stateless, the need to maintain an association with a transaction is alleviated. There's actually no strong reason why the session even needs to know about the transaction (and vice-versa). There could even be just one sateless entity manager object in the whole program. (Hell, in principle you could even stick all those operations on In what you're describing, there's still a hard link between the lifecycle of sessions and transactions. The sessions are not really truly stateless, even though they don't have a persistence context. |
Kind of. Hibernate StatelessSession as I see it has a lot of limitations and maybe that is what you are really going for. What I'm more suggesting is about is having almost ALL the functionality of a normal JPA usage but with some adjustments like:
We can turn on or off the use of JDBC batch per transaction (and a global default) and specify per transaction a JDBC batch size to use so the app code can decide and has full control. In my API there is actually a
Absolutely agree. Hence there is also the case for per transaction turning off GetGeneratedKeys, turning off cascading persist, controlling flush on query behaviour - it gives the app code exact control. If for example we turn off batch and turn off cascading persist we'd get the behaviour of the original proposal / StatelessSession.
Yes if JDBC batch is used. flush() is called or some batch size is hit or flushOnQuery is on and a query is executed etc. Pretty much JPA except we have the ability per transaction to control JDBC batch, cascade persist etc.
App code gets to control global defaults and control these things per transaction. I'd argue there is no loss of control and as you allude to this level of control isn't available to us via JPA today.
Well yes exactly except we wouldn't call it EntityManagerFactory ... hence my comment:
Well maybe I'm missing the point here. In this API I'm talking about there are "Transactions" and there is the " When we go When we go: List<CustomerEntity> customers = sessionLessJPA.query(CustomerEntity.class)
.where() ...
.findList(); ... then if that is running in a transaction (which has an internal persistence context/L1 cache attached to it) then when we execute that query it will look to use the persistence context that is attached to that surrounding transaction.
Hmmm, we might be hitting a terminology issue given there is "JPA Persistence context" , "L1 cache" and "Hibernate Session" ... maybe what I'm suggesting is that it might be less useful to talk about Sessions per say because in my mind they do too many things (Unique instances as part of graph building/L1 cache, dirty state, persisting actions like flush). What I'm suggesting is to break that up and the only "thing" that is scoped with the transaction is the "persistence context" but only the part used for de-duplication as part of graph building, it's not part of persisting at all - in Hibernate terms I think that is the "L1 Cache". ... but I suspect I could be explaining this rather badly. |
Right, you put the operations on your equivalent of But it's an orthogonal question to the question of what are the semantics of these operations, and a question I'm not especially interested in initially. This isn't, it seems to me, an important difference.
Sure, but if you do have batching, then there is state implicitly or explicitly associated with the transaction. That's clear, because you have defined a In what I'm describing, there's never any such state, and there's no
I understand that. This is the same in Hibernate.
I understand that. This is the same in Hibernate.
Right, so what I'm arguing is that for this sort of API that's all you need. There's some value in having If you want transactional write-behind and transparent batching, you go full
Right, that's what I'm trying to get at here. We already have an API with all the fancy bells and whistles and lots of implicit behavior, and it works great. But sometimes some people like more direct control and that, IMO, calls for a separate API, because the semantics are naturally going to be quite a lot different to Indeed, the whole programming model is quite different. In JPA there's no explicit
Well wait, now I'm confused. Perhaps I'm misreading, but that doesn't sound sessionless at all. That sounds like you do have a persistence context, just one that is transparently associated to the transaction. That's not what this proposal is about at all. A The motivation, again, is:
I mean the way these terms are using in JPA (and Hibernate) is:
One thing is a hashmap full of entity instances, and the other thing is an API with operations like But this 1-to-1 relationship doesn't really apply to |
To clarify a minor point, since perhaps I left the wrong impression here. In a Hibernate classic But that's not the model in Hibernate Reactive, and it's not the model I'm proposing here. |
That's it, there is a persistence context that is transparently associated to the transaction. Its an API that does not have a EntityManager (or "Unit of Work" or "Hibernate Session big S"). "Sessionless ORM" is a terrible (and old) term and I'm dropping it. It might be better called "ORMs that transparently manage their persistence context" but the TLDR effect in terms of API is that there is no EntityManager and no entity bean lifecycle.
In my terminology this is "query scoped persistence context" (with the other scopes being "extended scope" and "transaction scope"). This is a option available on the query, for any given query we can choose for it to use "query scoped persistence context" and yes this has proven to be very useful for people.
Yes noting that today JPA does not give the application code control on a per transaction basis over use of use of jdbc batch, batch size, GetGeneratedKeys, and cascading of persist. JPA also uses "transparent persistence" meaning attached dirty entities are included as an update implicitly as opposed to explicit use of an update() method ("explicit persistence").
What I'm suggesting (and apologies because maybe it's confusing the issue) is that there is a more radical option that would achieve the same benefits in terms of control but I'd argue it does so without the proposed limitations of
I believe what you'd suggest is that we don't need anything more than What this more radical approach allows is a programming model where there is literally only transactions to scope (using try with resources). It is arguably orientated to be easier to use stand alone without a JEE container or Spring because there is no EntityManager to manage. I believe this approach is in reach of all the JPA vendors if they desire it. ... but it is more radical approach and maybe there isn't a lot of familiarity with it. |
I mean, I think this at least slightly overstates the difference. You still have an interface with explicit persistence operations, it's just effectively a singleton. And, if I understand correctly, you still have some sort of persistence context. (You need it, I believe, in order to avoid data aliasing.) I think what you're saying is that by default this persistence context is scoped to the transaction. Now, sure, in JPA the The only real difference in what you're describing, again assuming I understand correctly, is that in your case the association between the persistence context and the transaction is managed "internally" by your ORM implementation, rather than "externally" by CDI or whatever. If that's right, then in Hibernate you can achieve exactly the same thing using
On this point I might still be misunderstanding, since I don't know the details of your implementation. What precisely do you mean by this? Can a single entity instance be used in multiple transactions at the same time? If not, then I would say there's still a lifecycle. But I'm not sure I understand what you mean by the word "lifecycle".
Well, if all it does is achieve the same affect as a |
WRT jakarta-persistence-spec-3.0.pdf, 3.2. Entity Instance’s Life Cycle there is
So by "no entity bean lifecycle" I mean an entity bean does not need to be attached, managed or removed [from a persistence context or anything]. We can new up an entity bean and insert(), update() or delete() it [much like Hibernate StatelessSession]. When we fetch a bean, mutate it and then update() or delete() it's dirty state comes from the bean itself. [which might be the same with Hibernate when using enhancement?]. The crux of this distills down to having the dirty state on the entity beans themselves for update() and delete(). With Hibernate
What IF we want to use JPA without any container, just in Java SE? Lets say I am writing something small like a lambda and I don't want to use any container, how well is JPA suited to that today? That thought in my mind isn't too far away from ... What IF someone wants to only use the StatelessSession API? Would we need a container if we only used StatelessSession API? [I don't think we do]. I don't think what I'm suggesting is "technically radical" in the sense that it really is just moving functionality around under the hood and having a different API (with a caveat that it really desires dirty state to be held on the entity beans themselves). What is perhaps radical about it is that its really suggesting ... What could the API be if someone only wanted to use the StatelessSession API? |
Well a single instance has state and shouldn't be mutated concurrently ... so it's more like: can a single entity be say fetched in one transaction and then persisted in another transaction - yes. [the dirty state is on the entity beans themselves]. In the case of nested transactions (savepoints), we can have some beans mutated and update() them in a nested transaction, if that fails we shouldn't use those beans that failed to persist per say but there isn't a need to "clear a persistence context" etc and we can carry on processing. We can new up and entity bean and update() them - this is an update without any dirty state. Said differently, we can materialize an entity bean graph from say json and update() it. I believe this would all be the same as |
While playing around with "Hibernate + StatelessSession" versus "Jakarta Persistence + EntityManager" possibilities to directly perform persistence operations I found this proposal. I also found some real life issues I also ran into, which might be useful for this issue. Especially the last item is a good read about where to add a 'StatelessSession' API / interface: in Quarkus (Panache)? in Hibernate? or as in this issue: in Jakarta persistence?
|
Can I read this proposal as to provide an API for direct JDBC with mapping through JPA to a certain extent? Assuming we add 4 proposed methods ( I'm not in favor of adding these new |
When talking about API of this new feature, I have few more notes:
And few notes to transactions.
Flow API design for queries.
...I know, too simple for transaction, but it's jut an example.
|
It would be really cool to include this in the JPA spec, I agree 100%. 👍 IMO: The advantage is that all operations become explicit minimizing resources, no object is temporarily stored in memory during a transaction without you knowing (cache L1), and Lazy Fetching operations becomes also explicit, one "disadvantage" is lose the cache L1 and L2, however if the application is well written, it won't need the first-level cache nor the second-level cache as it ends up being mitigated by a Redis or Infinispan for instance. |
For some more "prior art", EF Core has a concept of tracking vs no-tracking queries, and surfaces this functionality via a |
for jakartaee#374 Signed-off-by: Gavin King <[email protected]>
for jakartaee#374 Signed-off-by: Gavin King <[email protected]>
A feature that Hibernate has had for a very long time, but which has never made it into the JPA spec is
StatelessSession
.https://docs.jboss.org/hibernate/orm/6.1/javadocs/org/hibernate/StatelessSession.html
The idea of a stateless session is to allow the program to directly perform persistence operations without going via a persistence context, much like what you would get with handcoded SQL:
clear()
the persistence context when performing operations which affect a large number of entity instances.Now, the flipside is that the semantics differ in significant ways to the semantics of a regular
EntityManager
:@OneToMany
), since it's hard to come up with an efficient way to implement that without a persistence context for the common usecase.To emphasize these semantic differences, the persistence operations are given distinct names:
insert()
(notpersist()
)delete()
(notremove()
)update()
get()
(notfind()
)(Though
refresh()
is still calledrefresh()
.)There are two additional (minor) limitations, though they're not fundamental to the idea, and could in principle be removed: there's no cascading of persistence operations, and there's no persistence callback events. These features don't seem terribly useful for stateless sessions but nor would they be difficult to implement.
I think that it's well-worth discussing whether a stateless entity manager API should be introduced in the JPA spec.
The text was updated successfully, but these errors were encountered: