JPA: Can I force objects to be persisted to the database in the order that the objects were sent to persist()?
I am creating a billing system which manages a Ledger for each customer. Each Ledger has a list of LedgerEntry's which records each customer transaction.
As I was 开发者_运维知识库testing this, I noticed that if I created a bunch of LedgerEntry's within the same transaction, the @Id value was not in order that the objects were given to em.persist(), unless I did a em.flush() after each entry is created.
Because I rely on the order of the id for the proper behavior of the Ledger (specifically, the current balance is the last LedgerEntry in the list -- enforced by a @OrderBy "id ASC"), this means that I have to flush() a bunch of times.
Is there a way to avoid flushing after each row is created? That is, to have some sort of ordering on how the objects are persisted, without using an @OrderColumn?
I'm not sure if this is generic JPA or Hibernate-only, but note the difference between Hibernate's persist()
and save()
. From the docs (emphasis mine):
persist()
makes a transient instance persistent. However, it does not guarantee that the identifier value will be assigned to the persistent instance immediately, the assignment might happen at flush time.- save() does guarantee to return an identifier. If an INSERT has to be executed to get the identifier ( e.g. "identity" generator, not "sequence"), this INSERT happens immediately, no matter if you are inside or outside of a transaction.
So if you use save()
, IDs should be generated in the order that you invoke save()
. If doing an insert just to get the ID is problematic (for performance etc) you can choose to use another ID generator which doesn't require hitting the database.
I'm not totally sure I understand your structure.
You should be able to sort a single business transaction (multiple LedgerEntry rows?) by transaction_timestamp to get the proper order.
But why is the current balance row in the table at all? It could be computed, yes?
If the current balance row has to be there, I'd create a line_item_number column and assign the values sequentially for each business transaction. This will probably make a nice unique key when coupled with the Ledger id. And I'd assign the current balance row a magic value of 999999 so it would always sort last.
A nice side effect is that picking the amount owed from the table is real easy - sum all rows where line_item_number = 999999.
My preference, however, would be to remove the current_balance row and see the problems just go away.
Another option is to put the computer current_balance in the Ledger and not have it as a LedgerEntry row.
And the ids are generated using what method ? sequence ? table ? user-assigned ? There is no guarantee, as mentioned by other replies, of order of assignment, but if the generation method is not dependent on the datastore then most implementations will set the value at the time of the call to "persist". If the generation method OTOH uses the datastore then you will have to flush to force this datastore contact.
To my knowledge, there is nothing about the ordering of statements and the order of EntityManager
method calls in the spec. So I would really not rely on that, even if your provider allows to be configured via some kind of proprietary settings or offers this behavior by default.
But actually, if you need to deal with ordered list, it would IMO make a lot of sense to use a column to maintain the persistent order and to define it using an OrderColumn
. You should IMO not rely on the PK for business things.
精彩评论